Service精通


本節引言:

本節,我們繼續來研究Service(服務)元件,本節將會學習下Android中的AIDL跨進程通訊的一些 概念,不深入原始碼層次,暫時知道是什麼,會用即可!開始本節內容~ 本節對應官方文件:Binder


1.Binder機制初涉


1)IBinder和Binder是什麼鬼?

我們來看看官方文件怎麼說:

1.png

中文翻譯:

IBinder是遠端物件的基本接口,是餓了高效能而設計的輕量級遠端呼叫機制的核心部分。但他 不僅用於遠端調用,也用於進程內調用。此介面定義了與遠端物件間互動的協定。但不要直接實現 這個接口,而是繼承(extends)Binder

IBinder主要的API是transact(),與之對應的API是Binder.onTransact()。透過前者,你能 想遠端IBinder物件發送發出調用,後者使你的遠端物件能夠回應接收到的調用。 IBinder的API都是Syncronous(同步)執行的,例如transact()直到對方的Binder.onTransact()方法呼叫玩 後才返回。 呼叫發生在進程內時無疑是這樣的,而在進程間時,在IPC的幫助下,也是同樣的效果。

透過transact()傳送的資料是Parcel,Parcel是一種一般的緩衝區,除了有資料外還帶有 一些描述它內容的元資料。元資料用於管理IBinder物件的引用,這樣就能在緩衝區從一個行程移動 到另一個進程時保存這些引用。這樣就保證了當一個IBinder被寫入到Parcel並發送到另一個進程中, 如果另一個進程把同一個IBinder的引用回傳到原來的進程,那麼這個原來的進程就能接收到發出的 那個IBinder的引用。這種機制使IBinder和Binder像唯一標誌符一樣在進程間管理。

系統為每個行程維護一個存放互動執行緒的執行緒池。這些交互執行緒用來派送所有從另外行程發來的IPC 調用。例如:當一個IPC從行程A發到行程B,A中那個發出呼叫的執行緒(這個應該不在執行緒池中)就阻塞 在transact()中了。進程B中的交互線程池中的一個線程接收了這個調用,它調用Binder.onTransact(),完成後用一個Parcel來做為結果返回。然後進程A中的那個等待的線程在 收到回傳的Parcel後得以繼續執行。實際上,另一個進程看起來就像是當前進程的線程, 但不是當前進程創建的。

Binder機制也支援進程間的遞歸呼叫。例如,行程A執行自己的IBinder的transact()呼叫程序B 的Binder,而進程B在其Binder.onTransact()中又用transact()向進程A發起調用,那麼進程A 在等待它發出的呼叫回傳的同時,也會用Binder.onTransact()回應行程B的transact()。 總之Binder造成的結果就是讓我們感覺到跨進程的呼叫與進程內的呼叫沒什麼兩樣。

當操作遠端物件時,你經常需要查看它們是否有效,有三種方法可以使用:

  • 1 transact()方法將在IBinder所在的進程不存在時拋出RemoteException異常。
  • 2 如果目標程序不存在,那麼呼叫pingBinder()時回傳false。
  • 3 可以用linkToDeath()方法向IBinder註冊一個IBinder.DeathRecipient, 在IBinder代表的程序退出時被呼叫。

PS:中文翻譯摘自: Android開發:什麼是IBinder

好吧,估計你看完上這一串東西可能雲裡霧裡的,這裡簡單的小結下:

IBinder是Android給我們提供的一個進程間通訊的一個接口,而我們一般是不直接實作這個接口的,而是透過繼承Binder類別來實現進程間通訊!是Android中實現IPC(進程間通訊)的一種方式!


2)Binder機制淺析

2.png

#Android中的Binder機制由一系列系統元件組成:Client、Server、Service Manager和Binder驅動程式

大概呼叫流程如下,另外Service Manager比較複雜,這裡不詳細研究!

流程解析:

-> Client呼叫某個代理介面中的方法時,代理介面的方法會將Client傳遞的參數打包成Parcel物件;
-> 然後代理介面把該Parcel物件傳送給核心中的Binder driver;;
-> 然後Server會讀取Binder Driver中的請求數據,假如是發送給自己的,解包Parcel對象, 處理並將結果傳回;
PS:代理介面中的定義的方法和Server中定義的方法是一一對應的, 另外,整個呼叫過程是一個同步的,即Server在處理時,Client會被Block(鎖)住! 而這裡說的代理介面的定義就是等下要說的AIDL(Android介面描述語言)!


3)為何Android使用Binder機制來實現進程間的通訊?

  1. 可靠性:在行動裝置上,通常採用基於Client-Server的通訊方式來實現網路與裝置間的內部通訊。目前linux支援IPC包含傳統的管道,System V IPC,即訊息佇列/共享記憶體/訊號量,以及socket中只有socket支援Client-Server的通訊方式。 Android系統為開發者提供了豐富進程間通訊的功能接口,媒體播放,感測器,無線傳輸。這些功能都由不同的server來管理。開發都只在乎將自己應用程式的client與server的通訊建立起來便可以使用這個服務。毫無疑問,如若在底層架設一套協定來實現Client-Server通信,增加了系統的複雜性。在資源有限的手機 上來實現這種複雜的環境,可靠性難以保證。
  2. 傳輸效能:socket主要用於跨網路的進程間通訊和本機上進程間的通信,但傳輸效率低,開銷大。訊息佇列和管道採用儲存-轉送方式,即資料先從發送方快取區拷貝到核心開闢的一塊快取區中,然後從核心快取區拷貝到接收方快取區,其過程至少有兩次拷貝。雖然共享記憶體無需拷貝,但控制複雜。比較各種IPC方式的資料拷貝次數。共享記憶體:0次。 Binder:1次。 Socket/管道/訊息隊列:2次。
  3. 安全性:Android是一個開放式的平台,所以確保應用程式安全是很重要的。 Android對每一個安裝應用程式都分配了UID/PID,其中進程的UID是可用於鑑別進程身分。傳統的只能由使用者在資料包裡填寫UID/PID,這樣不可靠,容易被惡意程式利用。而我們要求由核心來添加可靠的UID。 所以,出於可靠性、傳輸性、安全性。 android建立了一套新的進程間通訊方式。 ——摘自:Android中的Binder機制的簡要理解

當然,作為一個初級的開發者我們並不關心上述這些,Binder機制給我們帶來的最直接的好處就是:我們不需要關心底層如何實現,只需按照AIDL的規則,自定義一個接口文件,然後調用調用接口中的方法,就可以完成兩個進程間的通訊了!


2.AIDL使用詳解


1)AIDL是什麼?

嘿嘿,前面我們講到IPC這個名詞,他的全名叫做:跨行程通訊(interprocess communication), 因為在Android系統中,個個應用程式都運行在自己的進程中,進程之間一般是無法直接進行資料交換的, 而為了實現跨進程,Android給我們提供了上面說的Binder機制,而這個機制使用的介面語言就是:AIDL(Android Interface Definition Language),他的語法很簡單,而這個介面語言並非真正的程式設計 語言,只是定義兩個進程間的通訊介面而已!而產生符合通訊協定的Java程式碼則是由Android SDK的 platform-tools目錄下的aidl.exe工俱生成,產生對應的介面檔案在:gen目錄下,一般是:Xxx.java的介面! 而在該介面中包含一個Stub的內部類,該類別中實作了在該類別中實作了IBinder介面與自訂的通訊介面, 這個類別將會作為遠端Service的回呼類別-實作了IBinder介面,所以可作為Service的onBind( )方法的回傳值!


2)AIDL實作兩個行程間的簡單通訊

在開始寫AIDL介面檔前,我們需要了解下編寫AIDL的一些注意事項:

AIDL注意事項:

  • 介面名詞需要與aidl檔案名稱相同
  • 介面和方法前面不要加訪問權限修飾符:public ,private,protected等,也不能用static final!
  • AIDL預設支援的類型包括Java基本型別String#ListMapCharSequence,除此之外的其他類型都 需要import聲明,對於使用自訂類型作為參數或返回值,自訂類型需要實作Parcelable接口, 詳情請看後面的傳遞複雜資料類型
  • 自訂類型和AIDL產生的其它介面類型在aidl描述檔中,應該明確import,即便在該類別和定義 的包在同一個包中。

另外,如果寫aidl你用的編譯器是:Eclipse的話要注意: 不要直接new file然後建立哦!這樣的話是打不開文件,從而不能寫程式碼哦!
①直接新建一個txt檔案,寫好後儲存為.aidl格式,然後複製到對應路徑下
②因為aidl和介面類似,所以直接new interface,編寫好內容後,來到對應java文件所在目錄下修改文件後綴名;

假如你使用的是Android Studio的話,不同於Eclipse,如果你按照Eclipse那樣創建一個AIDL文件,會發現 並沒有編譯產生對應的XXX.java文件,AS下建立AIDL需要在main目錄下新建一個aidl資料夾,然後定義一個 和aidl包名相同的包,最後創建一個aidl文件,接著按ctrl + f9重新編譯,就可以了!

3.png

上面兩者成功編譯的結果如下,你可以分別在對應目錄下找到對應的AIDL檔

4.png5.png


#1.服務端:

Step 1:建立AIDL檔:

IPerson.aidl

package com.jay.aidl;

interface IPerson {
    String queryPerson(int num);
}

我們開啟IPerson.java看看裡面的程式碼:

IPerson.java

/*
* 此檔案是自動產生的。  請勿修改。 # */
package com.jay.aidl;
公用介面IPerson 擴充android.os.IInterface
{
/** 本機 IPC 實作存根類別。 */
公用靜態抽象類別Stub 擴充android .os.Binder 實作com.jay.aidl.IPerson
{
#private static final java.lang.String DESCRIPTOR = "com.jay.aidl.IPerson";
/** 建構存根並將其附加到介面。 * /
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* * 將 IBinder 物件投射到 com.jay.aidl.IPerson 介面中,
* 如果需要,則產生代理。 */
public static com.jay.aidl. IPerson asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface ( DESCRIPTOR);
if (((iin!=null)&&(iininstanceof com.jay.aidl.IPerson))) {
return ((com.jay.aidl.IPerson)iin);
}
return new com.jay.aidl.IPerson.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_queryPerson:
{
data.enforceInterface( DESCRIPTOR);
int _arg0;
_arg0 = data.readInt();
java.lang.String _result = this.queryPerson(_arg0);
#reply.writeNoException();
reply .writeString(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
私有靜態類別Proxy實作com .jay.aidl.IPerson
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder Remote)
{
mRemote = Remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public java.lang.String queryPerson(int num) 拋出android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
嘗試{
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(num);
mRemote.transact(Stub.TRANSACTION_queryPerson, _data, _reply, 0);
_reply.readception();#Exception();#Exception();# #_result = _reply.readString();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
} }
}
static final int TRANSACTION_queryPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public java.lang.String
}

這裡我們關注的只是**asInterface(IBinder)**和我們定義的介面中的**queryPerson()**方法!

該方法會把IBinder類型的物件轉換成IPerson類型的,必要時產生一個代理對象回傳結果!

其他的我們可以不看,直接跳過,進行下一步。

Step 2:**自訂我們的Service類別,完成下述操作:

1)繼承Service類別,同時也自訂了一個PersonQueryBinder類別用來繼承IPerson.Stub類別就是實作了IPerson介面與IBinder介面

#2)實例化自訂的Stub類別,並重寫Service的onBind方法,回傳一個binder物件!

AIDLService.java

package com.jay.aidlserver;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import com.jay.aidl.IPerson.Stub;

/ **
 * 由 Jay 創立於 2015 年 8 月 18 日 0018。 */
public class AIDLService extends Service {

    private IBinder binder = new PersonQueryBinder();# ,"基神","J神","翔神"};

    private String query(int num)
    {
     
            return names[num - 1];
        }
        public IBinder onBind(Intent intent) {
#        return null;
    }

    private final class PersonQueryBinder extends Stub{
        ception {
            return query(num);
        }
    }
}

Step 3:在AndroidManifest.xml檔案中註冊Service


             ;
                
       />
            < /intent-filter>
        

這裡我們並沒有提供Activity介面,但改應用提供的Service可以讓其他app來呼叫!


2.客戶端
直接把服務端的那個aidl檔案複製過來,然後我們直接在MainActivity中完成,和綁定本地Service的操作
有點類似,流程如下:
1)自訂PersonConnection類別實作ServiceConnection介面
2)以PersonConnection物件作為參數,呼叫bindService綁定遠端Service
bindService(service ,conn,BIND_AUTO_CREATE);
ps:第三個參數是設定如果服務沒有啟動的話,自動建立
3)和本機Service不同,綁定遠端Service的ServiceConnection並不能直接取得Service的onBind( )方法
傳回的IBinder對象,只能傳回onBind( )方法所傳回的代理物件,需要做如下處理:
iPerson = IPerson.Stub.asInterface(service);
再接著完成初始化,以及按鈕事件等就可以了

具體程式碼如下:

MainActivity.java

#
package com.jay.aidlclient;

import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
導入android.os.IBinder;
導入android.os.RemoteException;
導入android.support.v7.app.AppCompatActivity;
導入android.view.View;
導入android.widget.Button;
導入android.widget.EditText;
導入android.widget.TextView;

導入com.jay.aidl.IPerson;

#public class MainActivity 擴充AppCompatActivity 實作View.OnClickListener{

    private EditText edit_num;
    private Button   iPerson;
    private PersonConnection conn = new PersonConnection();

##    @Override
    protected void onCreate(Bundle savedInstanceState) {
  View(R.layout.activity_main);
bindViews();
        //綁定遠端Service
        Intent service = new Intent("andP.int遠.Lamp.Sat;
##        bindService(service, conn, BIND_AUTO_CREATE);
        btn_query.setOnClickList {
        edit_num = (EditText) findViewById ( R.id.edit_num);
        btn_query = (按鈕) findViewById(R.id.btn_query);
    

    @Override
    public void onClick(View v) {
        String number = (number);
        try {
            txt_name.setText(iPerson.queryPerson(num));
        } catch (RemoteException e) {
             edit_num.setText("");
    }

    私有最終類別PersonConnection 實作ServiceConnection {
        public void onServiceConnected(ComponentName name, IBinder  {## (service);
        }
        public void onServiceDisconnected(ComponentName name) {
            iPerson = null;
        }
  

接下來先啟動AIDLServivce,然後再啟動AIDLClient,輸入查詢序號,即可取得對應名稱! 當然也可以直接啟動AIDLClient,也會得到相同效果:

效果圖如下:

6.gif


##3)傳遞複雜資料的AIDL Service

上面的例子我們傳遞的只是要給int類型的參數,然後服務端回傳一個String類型的參數,看似滿足 在我們的基本需求,不過實際開發中,我們可能需要考慮傳遞複雜資料類型的情況!下面我們來學習下 如何向服務端傳遞複雜資料類型的資料!開始之前我們先來了解

Parcelable介面

——Parcelable介面簡介:

相信用過序列化的基本上都知道這個介面了,除了他還有另外一個Serializable,同樣是用於序列化的, 只是Parcelable更加輕量級,速度更快!但寫起來就有點麻煩了,當然如果你用的as的話可以用 的外掛程式來完成序列化,例如:

Android Parcelable Code Generator當然,這裡我們還是手把手教大家來實作這個介面~

##首先

要實作:writeToParcelreadFromPacel方法 寫入方法將物件寫入到包裹(parcel)中,而讀取方法則從包裹中讀取物件, 請注意,寫入屬性順序需與讀取順序相同

接著

需要在:該類別中新增一個名為CREATORstatic final屬性 改屬性需要實作:android.os.Parcelable.Creator介面

#再接著

需要從寫入介面中的兩個方法:createFromParcel (Parcel source)方法:實作從source建立出JavaBean實例的功能newArray(int size):建立一個類型為T,長度為size的陣列,只有一個簡單的return new T[size]; (這裡的T是Person類別)

最後,describeContents()

:這個我也不知道是拿來幹嘛的,直接回傳0即可!不用理他

——另外

非原始類型中,除了StringCharSequence以外,其餘均需要一個方向指示符。 方向指示符包括inout和inout。 in表示由客戶端設置,out表示由服務端設置,inout表示客戶端和服務端都設定了該值。

好的,接著來寫程式碼試試(AS這裡自訂型別有點問題,暫時還沒解決,就用回Eclipse~):

程式碼範例:

自訂兩種物件類型:Person與Salary,Person作為呼叫遠端的Service的參數,Salary作為傳回值! 那麼首先要做的就是創建Person與Salary類別,同時需要實作Parcelable介面

1.——服務端

Step 1:創建Person.aidl和Salary.aidl的文件,因為他們需要實作Parcelable接口,所以就下列語句:

Person.aidl:     parcelable Person; 
Salary.aidl:     parcelable Salary;
Step 2:分別建立Person類別與Salary類,需實作Parcelable接口,重寫對應的方法!


PS:因為我們後面是根據Person物件來取得Map集合中的資料,所以Person.java中我們重寫了hashcode和equals 的方法;而Salary類別則不需要!

Person.java:

#
package com.jay.example.aidl; 

import android.os.Parcel;
import android.os.Parcelable;

/**
 * 由 Jay 創立於 2015 年 8 月 18 日 0018。  */
public class Person implements Parcelable{
##    private Integer id;
    private String name;##1 id, String name) {
        this.id = id;
        this.name = name;#    return id;
    }

    public void setId(Integer id) {
        this.id = id;
    this.name = name;
    }

    public String getName() {
        return name;
   的,直接回傳0就行了
    @Override
    public int describeContents() {
        return 0;
#        return 0;
#      
    @Override
    public void writeToParcel(Parcel dest, int flags) {
              dest. writeString(name);
    }

卷public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>() {
      public Person createFromParcel(Parcel source) {
            return new Person(source.readInt(),        public Person[] newArray(int size) {
            return new Person[size];
        }
    )需要我們重寫hashCode()與equals()方法
    @Override
    public int hashCode()
    {
   int result = 1;
        result = prime * result + ((name == null) ? 0 : name.hashCode());
        return als(Object obj)
{
        if (this == obj)
            return true;
    return false;
        if (getClass() != obj.getClass())
            return false;
        Person other = (Person) ##            if (other.name != null)
               #        }
        else if (!name.equals(other.name))
         }
}
##<pre><p><strong>Salary.java</strong>~照葫蘆畫瓢</p>

<pre>
package com.jay.example.aidl ; 

import android.os.Parcel;
#import android.os.Parcelable;

/**
 * 由 Jay 創立於 2015 年 8 月 18 日 0018。 */
public class Salary 實作Parcelable {

    私有字串型別;
#    私有整數薪資;

    公用薪資() {
    }# 
  類型, #        this.type = 類型;
        this.salary }

    public Integereger getSalary() {
        返回薪資;
    }

    public void setType(String type    public void setSalary (Integer salary) {
        this.salary = salary;
    }

    @Override#  0;
    }

#    @Override
    public void writeToParcel(Parcel dest, int flags) {
         }

    public static final Parcelable.Creator<薪資> CREATOR = new Parcelable.Creator() {
        //從Parcel中讀取資料,Person物件
      
            return new Salary( source .readString(), source.readInt());
        }

@Override
        public Salary[] newArray(int size) {
           
    public String toString() {
        return "工作:" + type + "    薪水: " + salary;
    }
}


Step 3
:創建一個ISalary.aidl的文件,在裡面寫一個簡單的取得薪資資訊的方法:

package com.jay.example.aidl;

  
import com.jay.example.aidl.Salary;  
import com .jay.example.aidl.Person;  
interface ISalary  
{  
    //定義一個Person物件作為傳入參數  
  是傳入,所以前面有in  
    Salary getMsg(in Person owner);  
}

ps:這裡可以記得如果使用的是自訂的資料型別的話,需要import哦! ! !切記! ! !

Step 4:核心Service的編寫: 定義一個SalaryBinder類別繼承Stub,從而實作ISalary和IBinder介面;定義一個儲存資訊的Map集合! 重新onBind方法,傳回SalaryBinder類別的物件實例!

AidlService.java

package com.jay.example.aidl_complexservice;  
  
import java.util.HashMap;  
#import aid.util.Map;  
import com.
import aid.util.Map;  
import com. ISalary.Stub;  
import com.jay.example.aidl.Person;  
import com.jay.example.aidl.Salary;  
import android.app.Service  
import android.content. Intent;  
import android.os.IBinder;  
import android.os.RemoteException;  
  
public class AidlService ext
    private static Map< ;Person,Salary> ss = new HashMap<Person, Salary>();  
    //初始化Map集合,這裡在靜態程式碼區塊中進行初始化 
    {  
        ss.put(new Person(1, "Jay"), new Salary("碼農", 2000))); new Salary("歌手", 20000));  
        ss.put(new Person(3, "XM"), new Salary( , "MrWang"), new Salary("老師", 2000));  
    }  
      
 reate() {  
        super.onCreate();
        salaryBinder = new SalaryBinder();  
    }  
#       {  
        return salaryBinder;  
    }  
  
      
#    //同樣是繼承Stub,即同時實現ISalary介面與IBinder介面  
    public class SalaryBinder extends Stub  
    { extends Stub  
           @Override
        public Salary getMsg(Person owner) 拋出 RemoteException {  
         
        }  
#    }  
      所
      System.out.println("服務結束!");  
        super.onDestroy();  
    }  
}

#註冊下Service:

  
    <意圖過濾器>    
          
        ;  
        
</服務>

#

2-客戶端寫

Step 1:把服務端的AIDL檔案拷貝下,拷貝後目錄如下:

7.jpg

Step 2:寫簡單的佈局,再接著就是核心MainActvitiy的實作了 定義一個ServciceConnection物件,重寫對應方法,和前面的普通資料的類似 再接著在bindService,然後再Button的點擊事件中取得Salary物件並顯示出來!

MainActivity.java

package com.jay.example.aidl_complexclient;  
  
import com.jay.example.aidl.ISalary;  
import com.jay.example.aidl.Person;  
導入 com.jay.example.aidl.Salary;  
  
#導入android.app.Activity;  
導入android.app.Service;  
import android.content.ComponentName;  
導入android.content.Intent;  
導入 android.content.ServiceConnection;  
導入android.os.Bundle;  
導入 android.os.IBinder;  
import android.os.RemoteException;  
導入 android.view.View;  
導入android.view.View.OnClickListener;  
導入 android.widget.Button;  
導入android.widget.EditText;  
導入android.widget.TextView;  
  
#  
public 類別 MainActivity 擴充 Activity {  
  
    private ISalary salaryService;  
    私人按鈕 btnquery;  
    私人 EditText 編輯名稱;  
    私人 TextView textshow;  
    private ServiceConnection conn = new ServiceConnection() {  
           
   ed(ComponentName name) {  
            salaryService = null;  
        }  
#          
        ) service) {  
            //所回復的是代理對象,且要呼叫這個方法!  
            salaryService = ISalary.Stub.asInterface(service);  
        }  
    };  
      
      
    @Override  
    protected void onCreate(Bundle saved#InstanceState){  protected void onCreate(Bundle saved#InstanceState){  #        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  
          
        btnquery = (按鈕) findViewById(R.id.btnquery);  
        editname = (EditText) findViewById(R.id.editname);  
        textshow = (TextView) findViewById(R.id.textshow);  
          
         Intent = new Intent();  
        it.setAction("com.jay.aidl.AIDL_SERVICE");  
        bindService(it, conn, Service.BIND_AUTO_CREATE);  
          
        btnquery.setOnClickListener(new OnClickListener() {   ##            public void onClick(View v) {  
             {  
                    字串且為 = editname.getText( ) .toString();  
                   Salary salary = salary    textshow.setText(name + salary.toString());  
                }catch(RemoteException e) { e.printStackTrace();}  
            }  
        });  
          
    }  
    @Override  
    protected void onDestroy()  
    protected void onDestroy() {{ #        super.onDestroy();  
        this.unbindService(conn);  
    }  
      
}
#

運行截圖:

8.jpg

#PS: 這裡的程式碼是之前用Eclipse寫的程式碼,Android Studio下自訂類型有點問題, 暫時沒找到解決方法,知道的朋友請告知下! ! !萬分感激! ! ! 出現的問題如下:

9.png

兩個實例的程式碼下載(基於Eclipse的):
1)使用AIDL完成進程間的簡單通訊
2)傳遞複雜資料的AIDL Service的實現


3.直接透過Binder的onTransact完成跨進程通信

上面講過Android可以透過Binder的onTrensact方法來完成通信,下面就來簡單試下,還是前面那個根據 序號查詢名字的範例:

服務端實作

/**
 * 由 Jay 創立於 2015 年 8 月 18 日 0018。 */
public class IPCService 擴充功能 Service{

#    private static final String DESCRIPTOR = "IPCService";#> "B神","艹神","基神","J神","翔神"};
    private MyBinder mBinder = new MyBinder();

 private MyBinder擴充Binder {
        @Override
        protected boolean onTransact(int code, Parcel data, Parcel rep (code){
                case 0x001: {
          forceInterface(DESCRIPTOR);
                    int num = data.readInt(I ;
                     reply.writeString(names[num]);
     true          }
            }
            return super.onTransact(程式   @Override
    public IBinder onBind(Intent intent) {
返回 mBinder;
    }
}

客戶端實作

公共類別 MainActivity 擴充 AppCompatActivity 實作 View.OnClickListener{

    私有 EditText edit_num;
    inder;
private ServiceConnection PersonConnection = new ServiceConnection()
    {
        @Override
                  mIBinder = null;
        }

#        @覆蓋
        public void onServiceConnected(ComponentName name, IBinder service)
        { }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState ) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity1  //綁定遠端Service
        Intent service = new Intent("android.intent.action.IPCService");
        service.setPackage("com.jay.ipcserver");
        .setOnClickListener(this) ;
    }

    private void bindViews() {
        edit_num =  = (Button) findViewById(R.id.btn_query) ;
        txt_result = (TextView) findViewById(R.id.txt_result);
    }

   int num = Integer.parseInt(edit_num.getText().toString());
        if (mIBinder == null)
   , "未連接服務端或服務端被異常殺死", Toast.LENGTH_SHORT).show();
        } else {
                 android.os.Parcel _reply = android.os.Parcel.obtain();
            String _result = null;
       writeInterfaceToken("IPCService");
                _data.writeInt(num);
        transact(0x001, _data, _reply, 0);
                _reply.read );
                txt_result.setText(_result);
         txt  );
            }catch (RemoteException e)
            {
                e.printStackTrace();
            } finally
            {
                _reply.recycle();
                _data.recycle() ;
            }
#        }
    }
}

#}

#

程式碼比較簡單,就不多解釋了~用到自己改改即可! PS:程式碼參考於:Android aidl Binder框架淺析


#4.Android 5.0後Service一些要注意的地方:

今天在隱式啟動Service的時候,遇到這樣一個問題

10.png

#然後程式一啟動就崩了,後來苦扣良久才發下是Android 5.0惹的禍, 原來5.0後有新的特性,就是:Service Intent  must be explitict! 好吧,就是不能隱式去啟動Service咯,解決的方法也很簡單! 例如StartService的:

startService(new Intent(getApplicationContext(), "com.aaa.xxxserver"));這樣寫程式直接crash掉,要寫成下面這樣: startService(new Intent(getApplicationContext(), LoadContactsService.class));

如果是BindService的:Intent service = new Intent("android.intent.action.AIDLService");的基礎上,要加上套件名稱:service.setPackage("com.jay.ipcserver");這樣就可以了~

官方文件:http://developer.android.com/intl/zh-cn/guide/components/intents-filters.html#Types文件說明處:

11.png


本節小結:

好的,關於Service的最後一節就到這裡,本節講解了Binder的基本概念以及實現進程間通信的 兩種方式:透過AIDL以及Binder.onTransact()來實現跨進程通訊!最後也講解了下Android 5.0後 使用Service不能隱含啟動的注意事項!就到這裡,謝謝~

#