一.相關(guān)介紹
在 Android 系統(tǒng)中,進(jìn)程間通信 (IPC) 是一種很重要的機(jī)制予弧。IPC 產(chǎn)生的原因是某些情況下需要在兩個進(jìn)程之間進(jìn)行一些數(shù)據(jù)的交換刮吧。而在深入學(xué)習(xí) Android 的過程中難免會遇到 IPC 的相關(guān)問題,比如常見的有在自己的應(yīng)用程序中讀取手機(jī)聯(lián)系人的信息杀捻,這就涉及到 IPC 了水醋。因為自己的應(yīng)用程序是一個進(jìn)程拄踪,通訊錄也是一個進(jìn)程惶桐,只不過獲取通訊錄的數(shù)據(jù)信息是通過 Content Provider 的方式來實(shí)現(xiàn)的姚糊。
對于初學(xué)者來說,在一開始接觸 IPC 時可能會摸不著頭腦肠槽,因為網(wǎng)上很多博客在講 Android IPC 時通常都是長篇大論秸仙,沒有從例子著手寂纪±痰埃基于以上種種原因以及希望對 AIDL 有一個更深入的理解拟杉,本篇博文就誕生了啼染。在 Android 系統(tǒng)中,IPC 的方式有很多種卦洽,比如有 Messenger 该窗、AIDL 和 ContentProvider 等酗失。我們今天就來講講其中的 AIDL ,AIDL 也是比較常見和經(jīng)常使用的一種 IPC 方式拖刃。希望讀者在看完本篇之后對于 AIDL 有一個比較深入的理解兑牡。
什么是 AIDL均函?
AIDL 的全稱是 Android Interface Definition Language(即 Android 接口定義語言)经柴。
AIDL是Binder的實(shí)例坯认。
AIDL的使用實(shí)例:
我們來模擬一下需要進(jìn)行 IPC 的情況牛哺,現(xiàn)在有客戶端和服務(wù)端引润,客戶端通過 AIDL 來和服務(wù)端進(jìn)行 IPC 淳附。我們假定現(xiàn)在客戶端需要傳一個 Person 類的對象給服務(wù)端奴曙,之后服務(wù)端回傳給客戶端一個 Person 類的集合。
1.服務(wù)端的相關(guān)代碼
以下 Person.aidl 文件:
parcelable Person;
注意在 IPC 機(jī)制中傳遞的自定義對象需要序列化坤溃,所以要實(shí)現(xiàn) Parcelable 接口。在 AIDL 文件中使用parcelable關(guān)鍵字聲明汁政。有了 Person.aidl 之后烂完,我們就要創(chuàng)建 AIDL 接口了。
interface AddPersonInter {
List<Person> addPerson(in Person person);
}
在 IMyAidlInterface.aidl 里嘶窄,主要聲明一個用于添加 Person 對象的抽象方法吻谋。另外漓拾,需要注意以下幾點(diǎn):
1.Person 類需要手動去 import ,在 AIDL 文件中不能自動導(dǎo)包低千;
2.在addPerson方法里需要聲明參數(shù)是 in 的,用來表示該參數(shù)是傳入的救拉。除了 in 之外拂铡,還有 out 和 inout 斗锭;
下面我們要創(chuàng)建一個 Service 用于和客戶端進(jìn)行 IPC 岖是。這里還要把該 Service 運(yùn)行在一個新的進(jìn)程里。我們只要在 AndroidManifest.xml 中聲明android:process=":remote"就行了。
public class MyService extends Service {
private static final String TAG = "MyService";
private List<Person> persons = new ArrayList<>();
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
Log.e("aaaa", "binder綁定成功");
return binder;
}
private final IBinder binder = new AddPersonInter.Stub() {
@Override
public List<Person> addPerson(Person person) throws RemoteException {
synchronized (persons) {
persons.add(person);
Log.e("aaaaa", "服務(wù)端 name----" + person.getName() + " age===" + person.getAge());
return persons;
}
}
};
}
在上面的代碼中我們可以看到在onBind(Intent intent)方法中返回了 mBinder 陆错,而客戶端正是通過這個 mBinder 來和服務(wù)端進(jìn)行 IPC 的对嚼。mBinder 是 IMyAidlInterface.Stub 匿名類的對象,IMyAidlInterface.Stub 其實(shí)是一個抽象類磨确,繼承自 Binder ,實(shí)現(xiàn)addPerson方法。這里要注意以下媳瞪,在addPerson的方法中需要將 persons 同步,這是因為在服務(wù)端 AIDL 是運(yùn)行在 Binder 線程池中的乍丈,有可能會有多個客戶端同時連接察蹲,這時候就需要同步以防止數(shù)據(jù)出錯宗收。
2.客戶端的相關(guān)代碼
客戶端需要將服務(wù)端的aidl文件夾整體復(fù)制到客戶端,并將用到到的java類Person.java復(fù)制到客戶端挑宠,
注意包名一致诡挂,不然編譯會報錯奴璃。
public class Main2Activity extends AppCompatActivity {
TextView tv;
private AddPersonInter addPersonInter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv=this.findViewById(R.id.tv);
// 啟動服務(wù)端的服務(wù)雳旅,并進(jìn)行綁定
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.seventeenok.test", "com.seventeenok.test.MyService"));
bindService(intent, conn, Context.BIND_AUTO_CREATE);
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
try {
List<Person> list = addPersonInter.addPerson(new Person("lizhi", 24));
} catch (RemoteException e) {
e.printStackTrace();
}
}
}).start();
}
});
}
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
addPersonInter = AddPersonInter.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
addPersonInter = null;
}
};
}
AIDL 的流程基本上就是這樣的型豁。通過這個簡單的例子驼壶,相信對于 AIDL 有了一個初步的了解。下面我們就要進(jìn)行 AIDL 的代碼分析。
3.項目的整體目錄結(jié)構(gòu):
AIDL代碼分析
工程中的 gen 目錄下找到對應(yīng) AIDL 編譯后的文件:
public interface AddPersonInter extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.seventeenok.test.AddPersonInter {
private static final java.lang.String DESCRIPTOR = "com.seventeenok.test.AddPersonInter";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.seventeenok.test.AddPersonInter interface,
* generating a proxy if needed.
*/
public static com.seventeenok.test.AddPersonInter asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.seventeenok.test.AddPersonInter))) {
return ((com.seventeenok.test.AddPersonInter) iin);
}
return new com.seventeenok.test.AddPersonInter.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_addPerson: {
data.enforceInterface(DESCRIPTOR);
com.seventeenok.test.Person _arg0;
if ((0 != data.readInt())) {
_arg0 = com.seventeenok.test.Person.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
java.util.List<com.seventeenok.test.Person> _result = this.addPerson(_arg0);
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.seventeenok.test.AddPersonInter {
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.util.List<com.seventeenok.test.Person> addPerson(com.seventeenok.test.Person person) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.seventeenok.test.Person> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((person != null)) {
_data.writeInt(1);
person.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.seventeenok.test.Person.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public java.util.List<com.seventeenok.test.Person> addPerson(com.seventeenok.test.Person person) throws android.os.RemoteException;
}
可以看到編譯后的 AddPersonInter.aidl 變成了一個接口,繼承自 IInterface 。在 AddPersonInter接口中我們發(fā)現(xiàn)主要分成兩部分結(jié)構(gòu):抽象類 Stub 和原來 aidl 中聲明的addPerson方法凰兑。
重點(diǎn)在于 Stub 類滩报,下面我們來分析一下。從 Stub 類中我們可以看到是繼承自 Binder 并且實(shí)現(xiàn)了 AddPersonInter接口侣姆。 Stub 類的基本結(jié)構(gòu)如下:
asInterface(android.os.IBinder obj)方法川蒙;
asBinder()方法昼牛;
onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)方法恬汁;
靜態(tài)類Proxy导狡,主要方法是addPerson(com.seventeenok.test.Person person)看彼;
靜態(tài)常量TRANSACTION_addPerson标锄;
asInterface(android.os.IBinder obj)
我們先從asInterface(android.os.IBinder obj)方法入手践剂,在上面的代碼中可以看到,主要的作用就是根據(jù)傳入的Binder對象轉(zhuǎn)換成客戶端需要的 AddPersonInter接口。如果服務(wù)端和客戶端處于同一個進(jìn)程匕争,那么該方法得到的就是服務(wù)端 Stub 對象本身跑杭,也就是上面 AIDL 例子 MyService 中的 mBinder 對象;否則返回的是系統(tǒng)封裝后的 Stub.Proxy ,也就是一個代理類,在這個代理中實(shí)現(xiàn)跨進(jìn)程通信。
asBinder()
該方法就是返回當(dāng)前的 Binder 對象蚯舱。
onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
在onTransact方法中兄裂,根據(jù)傳入的 code 值會去執(zhí)行服務(wù)端相對應(yīng)的方法腥泥。其中靜態(tài)變量TRANSACTION_addPerson就是其中的 code 值之一(在 AIDL 文件中聲明的方法有多少個就有多少個對應(yīng)的 code )。其中 data 就是服務(wù)端方法中所需要的參數(shù),執(zhí)行完后帅戒,最后把方法的返回結(jié)果放入 reply 中傳遞給客戶端瞎访。若該方法返回 false ,那么客戶端請求失敗写烤。
Proxy中的addPerson(com.seventeenok.test.Person person)
Proxy 類是實(shí)現(xiàn)了 AddPersonInter接口暂衡,把其中的addPerson方法進(jìn)行了重寫读恃。在方法中一開始創(chuàng)建了兩個 Parcel 對象,其中一個用來把方法的參數(shù)裝入,然后調(diào)用transact方法執(zhí)行服務(wù)端的代碼,執(zhí)行完后把返回的結(jié)果裝入另外一個 Parcel 對象中返回。
看完上面方法的介紹谤草,我們回過頭來看看 AIDL 例子中實(shí)現(xiàn)的流程温学。在客戶端中通過 Intent 去綁定一個服務(wù)端的 Service 。在onServiceConnected(ComponentName name, IBinder service)方法中通過返回的 service 可以得到對應(yīng)的 AIDL 接口的實(shí)例黄痪。這是調(diào)用了asInterface(android.os.IBinder obj)方法來完成的桅打。
客戶端在onServiceConnected(ComponentName name, IBinder service)中得到的 service 正是服務(wù)端中的 mBinder 是嗜。當(dāng)客戶端調(diào)用 AIDL 接口時丽柿,AIDL 通過 Proxy 類中的addPerson來調(diào)用transact方法,transact方法又會去調(diào)用服務(wù)端的onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)方法涂召。onTransact方法是運(yùn)行在服務(wù)端的 Binder 線程池中的果正。在onTransact中根據(jù) code 執(zhí)行相關(guān) AIDL 接口的方法秋泳,方法的參數(shù)從 data 中獲取。執(zhí)行完畢之后把結(jié)果裝入 reply 中返回給客戶端。 AIDL 的流程基本上就是這樣子了。
總結(jié)
AIDL 在 Android IPC 機(jī)制中算得上是很重要的一部分饲握,AIDL 主要是通過 Binder 來實(shí)現(xiàn)進(jìn)程通信的。當(dāng)然频丘,上面只是簡單的例子分析AIDL的整個流程,并沒有涉及到死亡代理亮钦、權(quán)限驗證等功能馆截,有想法的同學(xué)可以深入研究下。