一、前言
在上一篇文章Android IPC機制(二):AIDL的基本使用方法中,筆者講述了安卓進程間通訊的一個主要方式,利用AIDL進行通訊,并介紹了AIDL的基本使用方法。其實AIDL方式利用了Binder來進行跨進程通訊,Binder是Android中的一種跨進程通訊方式,其底層實現原理比較復雜,限于筆者水平,不能展開詳談,所以這篇文章主要談談以AIDL為例,談談Binder的使用方法。
二、原理
上一篇文章中創建了一個IMyAidl.aidl文件,即接口文件,隨即編譯了該文件,生成了一個.java文件,該文件在gen目錄下:
打開該文件,得到如下代碼:
* This file is auto-generated. DO NOT MODIFY.
* Original file: G:\\Android\\Project\\MyAidl\\app\\src\\main\\aidl\\com\\chenyu\\service\\IMyAidl.aidl
*/
package com.chenyu.service;
public interface IMyAidl extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.chenyu.service.IMyAidl {
......
public void addPerson(com.chenyu.service.Person person) throws android.os.RemoteException;
public java.util.List<com.chenyu.service.Person> getPersonList() throws android.os.RemoteException;</span>
}
其中省略了一部分,我們先從大體上認識,然后在深入。
(1)從大體上看,該java文件是一個接口,繼承了IInterface接口,接著,聲明了一個靜態內部抽象類:Stub,然后是兩個方法,可以看到,這兩個方法分別是原IMyAidl.aidl文件內聲明的兩個方法。
(2)我們看回Stub類,它繼承了Binder,同時實現了IMyAidl。這個類實現了自己的接口!那么可想而知,該接口所聲明的addPerson,getPersonList方法,將會在Stub類得到實現,具體如何實現,我們展開Stub類:
public static abstract class Stub extends android.os.Binder implements com.chenyu.service.IMyAidl {
private static final java.lang.String DESCRIPTOR = "com.chenyu.service.IMyAidl";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {<span style="white-space:pre"> </span>// 1
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.chenyu.service.IMyAidl interface,
* generating a proxy if needed.
*/
public static com.chenyu.service.IMyAidl asInterface(android.os.IBinder obj) { // 2
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.chenyu.service.IMyAidl))) {
return ((com.chenyu.service.IMyAidl) iin);
}
return new com.chenyu.service.IMyAidl.Stub.Proxy(obj);
}
@Override
public android.os.IBinder asBinder() {<span style="white-space:pre"> </span>// 3
return this;
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {<span style="white-space:pre"> </span>// 4
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_addPerson: {
data.enforceInterface(DESCRIPTOR);
com.chenyu.service.Person _arg0;
if ((0 != data.readInt())) {
_arg0 = com.chenyu.service.Person.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addPerson(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_getPersonList: {
data.enforceInterface(DESCRIPTOR);
java.util.List<com.chenyu.service.Person> _result = this.getPersonList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.chenyu.service.IMyAidl {<span style="white-space:pre"> </span>// 5
...
}
static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); // 6
static final int TRANSACTION_getPersonList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
(3)從上往下地,我們逐個分析一下各個方法或者變量的作用:
①Stub()構造方法:此方法調用了父類Binder的attachInterface()方法,將當前的Interface與Binder聯系起來,由于傳遞了DESCRIPTOR這個參數,唯一標識了當前Interface。
②asInterface(IBinder obj) :靜態方法,傳遞了一個接口對象,該對象從哪里傳遞進來的呢?我們來看看上一章博客的客戶端代碼:
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d("cylog", "onServiceConnected success");
iMyAidl=IMyAidl.Stub.asInterface(service);
}
在這里,可以看到,調用了IMyAidl.Stub.asInterface(service)方法,即上面的②號方法,并且把service傳遞了進去,我們接著往下看:
public static com.chenyu.service.IMyAidl asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.chenyu.service.IMyAidl))) {
return ((com.chenyu.service.IMyAidl) iin);
}
return new com.chenyu.service.IMyAidl.Stub.Proxy(obj);
}
首先判斷obj是否有效,如果無效直接返回Null,說明客戶端與服務端的連接失敗了。接著調用了obj.queryLocalInterface(DESCRIPTOR)方法,為IInterface的對象賦值,注意到這里再次傳遞了DESCRIPTOR參數,可以猜測,這個方法應該是查找與當前Interface相關的一個方法,我們看看IBinder接口的queryLocalInterface()方法:
/**
* Attempt to retrieve a local implementation of an interface
* for this Binder object. If null is returned, you will need
* to instantiate a proxy class to marshall calls through
* the transact() method.
*/
public IInterface queryLocalInterface(String descriptor);
大概意思是說,根據descriptor的值,試圖為Binder取回一個本地的interface,其中local意思應為當前進程,如果返回值是null,那么應該實例化一個proxy類。在了解了obj.queryLocalInterface(DESCRIPTOR)方法后,我們再次回到asInterface(obj)方法,繼續往下看:接著是一個if判斷,主要判斷客戶端與服務端是否處于同一進程,如果處于同一進程,那么直接返回了Stub對象本身,如果不是同一個進程,那么就會新建一個Proxy代理類(下面會提到)。
③asBinder():此方法用于返回當前對象本身。
④onTransact(int code,Parcel data,Parcel reply,int flags):該方法一般運行在服務端中的Binder線程池中,即遠程請求會在該方法得到處理。傳遞的code值用于判斷客戶端的請求目標,是addPerson或者是getPersonList。我們以請求目標為addPerson()為例分析一下,提取其主要函數體如下:
case TRANSACTION_addPerson: {
data.enforceInterface(DESCRIPTOR);
com.chenyu.service.Person _arg0;
if ((0 != data.readInt())) {
_arg0 = com.chenyu.service.Person.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addPerson(_arg0);
reply.writeNoException();
return true;
}
首先聲明了_arg0是Person類的對象,接著,以data為參數調用了Person類的CREATOR.createFromParcel方法,反序列化生成了Person類,這也是為什么實現了Parcelable接口的類應該同時實現CREATOR,原來在這里調用了反序列化的方法。接著,調用this.addPerson(_arg0)方法,注意:這里的this代表當前的Binder對象,那么由Binder調用的addPerson(_arg0)方法,實際上是由綁定到Binder的service調用的,即服務端調用了自身的addPerson方法。為了方便明白,讓我們來回顧一下上一篇文章服務端的代碼:
private IBinder iBinder= new IMyAidl.Stub() {
@Override
public void addPerson(Person person) throws RemoteException {
persons.add(person);
}
@Override
public List<Person> getPersonList() throws RemoteException {
return persons;
}
};
是不是一下子就明白了?IMyAidl.Stub()實現的接口,其中的方法在服務端得到了實現:addPerson和getPersonList()。當在Binder線程池中,調用了this.addPerson()方法,實際上回調了服務端的addPerson方法,而底層到底是怎么實現的,限于筆者的水平,暫時不了解,等以后筆者再深入了解Binder的工作機制再回答這個問題。
好了,回到當前的類,我們繼續往下看:
⑤private static class Proxy:這里又出現了一個私有的靜態內部類,關于這個類將在接下來詳細講述。
⑥最后兩行代碼分別是兩個常量,標志了兩個方法,即上面提到的code值。
(4)Proxy類,也實現了IMyAidl接口,同時實現了addPerson和getPersonList的方法。而Proxy類在哪里被實例化的呢?是上面(3)②中,當客戶端與服務端不在同一個進程的時候,就會實例化這個代理類,并返回給客戶端。什么叫做代理類呢?所謂代理,即一個中介,客戶端拿到的實例,能操作服務端的部分功能,讓客戶端以為自己已經拿到了服務端的實例,其實不是,只是拿到服務端的一個代理而已。接下來我們展開該類,看看內部:
private static class Proxy implements com.chenyu.service.IMyAidl {
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 void addPerson(com.chenyu.service.Person person) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
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();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public java.util.List<com.chenyu.service.Person> getPersonList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.chenyu.service.Person> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getPersonList, _data, _reply, 0);<span style="white-space:pre"> </span> //①
_reply.readException();
_result = _reply.createTypedArrayList(com.chenyu.service.Person.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
這里關注兩個接口方法的實現:addPerson和getPersonList,這兩個方法已經多次出現了,而在代理類實現的這兩個方法,是運行在客戶端的!!!其主要實現過程是這樣的:當客戶端拿到代理類,調用addPerson或者getPersonList方法,首先會創建輸入型Parcel對象_data和輸出型Parcel對象_reply,接著調用①號代碼調用transact來發起RPC遠程請求,同時當前線程會被掛起,此時,服務端的onTransact會被調用,即上面所說的(3)④號代碼,當服務端處理完請求后,會返回數據,當前線程繼續執行知道返回 _result結果。
至此,對于IPC的方式之一——AIDL的原理已經剖析完畢,接下來總結一下:
1、客戶端發出綁定請求,服務端返回一個Binder對象,該對象能處理跨進程請求,而客戶端拿到的是Binder對象的引用,Binder的實體是在服務端的。客戶端執行asInterface()方法,如果客戶端和服務端處于同一進程,則直接返回服務端的Stub對象本身,如果處于不同進程,則返回的是Stub.proxy代理類對象。
2、客戶端發送遠程請求(addPerson或者getPersonList),此時客戶端線程掛起,Binder拿到數據后,對數據進行處理如在不同進程,會把數據寫入Parcel,調用Transact方法。
3、觸發onTransact方法,該方法運行在Binder線程池,方法中會調用到服務端實現的接口方法,當數據處理完畢后,返回reply值,經過Binder返回客戶端,此時客戶端線程被喚醒。
三、優化
最后說一說如何優化AIDL,上面提到,客戶端發送請求后,會被掛起,這意味著,如果處理數據的時間過長,那么該線程就一直等不到喚醒,這是很嚴重的,如果在UI線程發送請求,會直接導致ANR,所以我們需要在子線程發送異步請求,這樣才能避免ANR。還有一點,Binder可能是意外死亡的,如果Binder意外死亡,那么子線程可能會一直掛起,所以我們要啟用重新連接服務。有兩個方法,一個是給Binder設置DeathRecipient監聽,另一個是在onServiceDisconnected中重連服務。