當(dāng)發(fā)起一次AIDL調(diào)用時(shí)谷朝,是如何進(jìn)行進(jìn)程間切換的?都經(jīng)過了哪些步驟武花?有哪些重要方法圆凰?
下面我們用一個(gè)例子來具體看一下
這里假定我們聲明了一個(gè)aidl方法,如下:
interface IMyAidlInterface {
WeatherEntity queryWeather(in RequestData request, in ICallBack callback);
}
下面看一下体箕,一次完整的IPC過程是怎樣的专钉,中間都發(fā)生了哪些事情
第一步:綁定服務(wù),獲取服務(wù)端binder引用
Client綁定了service后累铅,首先會拿到server的一個(gè)Binder引用:
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// 我們這里拿到的對象其實(shí)就是其Stub的內(nèi)部類Proxy對象
myAidlInterface = IMyAidlInterface.Stub.asInterface(service);
上面這步轉(zhuǎn)化跃须,會根據(jù)當(dāng)前client和server是否為同一個(gè)進(jìn)程,如果是不同進(jìn)程娃兽,則返回其內(nèi)部Proxy代理對象
第二步:客戶端發(fā)起發(fā)起調(diào)用
result = myAidlInterface.queryWeather(request, callback);
調(diào)用目標(biāo)方法queryWeather()之后菇民,會按照上面所述走到Proxy.queryWeather()中,邏輯如下:
IMyAidlInterface.Stub.Proxy.queryWeather():
@Override
public com.example.myapplication.WeatherEntity queryWeather(com.example.myapplication.RequestData request,
com.example.myapplication.ICallback callback) throws android.os.RemoteException {
// 獲取兩個(gè)新的Parcel對象,_data是入?yún)⒌诹罚琠reply是返回值
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
com.example.myapplication.WeatherEntity _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((request!=null)) {
_data.writeInt(1);
// 序列化參數(shù)1 RequestData進(jìn)_data中
request.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
// 序列化參數(shù)2 Callback進(jìn)_data中
_data.writeStrongBinder((((callback!=null))?(callback.asBinder()):(null)));
// 發(fā)起遠(yuǎn)程調(diào)用阔馋,remote其實(shí)就是我們在onServiceConnected中,獲取到的IBinder對象复旬,
// 也就是server端的Binder對象垦缅,也就是說在這里將請求轉(zhuǎn)交給了server去處理
mRemote.transact(Stub.TRANSACTION_queryWeather, _data, _reply, 0);
_reply.readException();
// 這里readInt判斷是否為0,確認(rèn)請求是否成功驹碍,因?yàn)樵趕erver端,會判斷返回值result是否為null凡恍,
// 如果為null則writeInt(0)志秃,否則寫1
if ((0!=_reply.readInt())) {
_result = com.example.myapplication.WeatherEntity.CREATOR.createFromParcel(_reply);
} else {
_result = null;
}
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
首先初始化兩個(gè)parcel:傳入?yún)?shù)_data和返回參數(shù)_reply,
將方法中所有的參數(shù)都寫入_data中嚼酝,普通對象調(diào)用的是它的writeToParcel方法浮还,
如果參數(shù)是一個(gè)aidl接口即繼承了IInterface接口,則調(diào)用的是Parcel.writeStrongBinder方法闽巩。將所有參數(shù)都寫入后钧舌,就開始調(diào)用mRemote.transact()方法,這里的mRemote其實(shí)就是我們在onServiceConnected中涎跨, 獲取到的IBinder對象洼冻,即在server端的Service.onBind方法中返回的Binder對象。在這一步將請求轉(zhuǎn)交給了server去處理隅很。
第三步:Binder.transact()
下面看下Binder的transact()撞牢,這個(gè)方法攜帶code(也就是目標(biāo)方法標(biāo)示),入?yún)ata和返回值reply:
/**
* Default implementation rewinds the parcels and calls onTransact. On
* the remote side, transact calls into the binder to do the IPC.
*/
public final boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply,
int flags) throws RemoteException {
if (false) Log.v("Binder", "Transact: " + code + " to " + this);
if (data != null) {
data.setDataPosition(0);
}
boolean r = onTransact(code, data, reply, flags);
if (reply != null) {
reply.setDataPosition(0);
}
return r;
}
這里做了兩件事:
在調(diào)用onTransact()發(fā)起調(diào)用前后叔营,對參數(shù)data和返回值reply重置position屋彪,然后調(diào)用onTransact(),這下就到了我們服務(wù)端中.aidl自己實(shí)現(xiàn)的onTransact()方法了绒尊。
這個(gè)方法如果返回false畜挥,表示服務(wù)端沒有目標(biāo)方法,原因下面會分析
第四步:服務(wù)端onTransact()
服務(wù)端的onTransact()方法邏輯如下:
// 根據(jù)傳入的code參數(shù)確定需要調(diào)用哪個(gè)方法
case TRANSACTION_queryWeather: {
data.enforceInterface(descriptor);
com.example.myapplication.RequestData _arg0;
if ((0!=data.readInt())) {
// 反序列化參數(shù)1 RequestData
_arg0 = com.example.myapplication.RequestData.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
com.example.myapplication.ICallback _arg1;
// 反序列化參數(shù)2 Callback
_arg1 = com.example.myapplication.ICallback.Stub.asInterface(data.readStrongBinder());
// 然后調(diào)用我們在服務(wù)端service中實(shí)現(xiàn)的目標(biāo)方法婴谱,得到result
com.example.myapplication.WeatherEntity _result = this.queryWeather(_arg0, _arg1);
reply.writeNoException();
if ((_result!=null)) {
reply.writeInt(1);
// 將result序列化進(jìn)reply中
_result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else {
reply.writeInt(0);
}
return true;
}
在服務(wù)端的onTransact()方法中蟹但,會根據(jù)傳入的code參數(shù)調(diào)用對應(yīng)方法,如果沒有code相對應(yīng)勘究,則調(diào)用父類也就是Binder.onTransact()方法矮湘。
Binder.onTransact()中,判斷code如果為預(yù)置的幾種口糕,返回true缅阳,否則返回false,那我們傳入的code肯定不是預(yù)置的,所以這時(shí)候會返回false十办。
這時(shí)秀撇,可以明確一點(diǎn):
如果在調(diào)用時(shí),mRemote.transact()返回false向族,則表示服務(wù)端沒有目標(biāo)方法呵燕。
第五步,service中IMyAidlInterface.Stub子類實(shí)現(xiàn)的各個(gè)方法
經(jīng)過上面的調(diào)用件相,邏輯就已經(jīng)被我們在服務(wù)端service處理完了再扭,拿到結(jié)果reply后,通過Binder將其返回給client端夜矗。到這里為止泛范,一次IPC調(diào)用就結(jié)束了。
上面的流程可以用下圖來說明:
IPC調(diào)用時(shí)紊撕,序列化和反序列化不匹配的問題
這里有一個(gè)問題需要注意:從上面的過程可以看出罢荡,在一次IPC調(diào)用中,一個(gè)方法的所有參數(shù)对扶,都是寫在同一個(gè)Parcel中的区赵, 這就要求我們必須同步更新client和server端的序列化字段,保證二者一致浪南,否則就會出現(xiàn)因?yàn)樾蛄谢头葱蛄谢黄ヅ淞牛瑢?dǎo)致空指針等問題。
為了理解上面的問題逞泄,我們需要對Parcel有一個(gè)簡單的認(rèn)識患整。
Parcel可以用于跨進(jìn)程傳輸數(shù)據(jù),它提供了一套機(jī)制喷众,可以將序列化后的數(shù)據(jù)寫入一個(gè)共享內(nèi)存各谚,其他進(jìn)程通過Parcel從共享內(nèi)存中讀出字節(jié)流并反序列化成對象。
Parcel是一塊連續(xù)的內(nèi)存到千,并且會根據(jù)需要自動擴(kuò)展其大小昌渤。也就是說寫入數(shù)據(jù)時(shí),如果系統(tǒng)發(fā)現(xiàn)已經(jīng)超出了Parcel的存儲空間憔四,它會自動申請所需內(nèi)存并擴(kuò)展dataCapacity膀息。
在我們向parcel里讀寫值的時(shí)候,parcel內(nèi)部會維護(hù)一個(gè)dataPosition了赵,類似于數(shù)組的索引潜支,
序列化/反序列化都是嚴(yán)格按照這個(gè)position來執(zhí)行的,我們可以通過setDataPosition()方法來設(shè)置該值柿汛。
例如上面的例子冗酿,我們有兩個(gè)參數(shù)request和callback,假設(shè)client的request多了一個(gè)字段test, 這兩個(gè)參數(shù)按照定義順序依次寫入parcel中裁替,那在server反序列化時(shí)项玛,原本position應(yīng)該是callback的, 現(xiàn)在變成了新增字段test弱判,這就會導(dǎo)致callback無法正常序列化而變成null襟沮,影響正常功能。
如何規(guī)避因?yàn)樾蛄谢头葱蛄谢黄ヅ鋵?dǎo)致的問題昌腰?
- 在序列化時(shí)开伏,可以通過手動setDataPosition(),通過控制position 將新增字段寫進(jìn)固定的沒有使用過的位置遭商,從而使其不影響反序列化
- 在寫aidl接口時(shí)硅则,如果無法做到同時(shí)更新client和server,那可以考慮將常改變的參數(shù)株婴,例如request定義在后面,例如test(callback, request)暑认,從而避免因?yàn)閞equest的改變影響callback的序列化困介。
在AIDL調(diào)用中,如何判斷server端的目標(biāo)方法是否存在蘸际?
通過上面的分析可以明確座哩,當(dāng)客戶端在調(diào)用時(shí),mRemote.transact()返回false粮彤,則表示服務(wù)端沒有目標(biāo)方法根穷。
在系統(tǒng)根據(jù)aidl自動生成的java類中,該方法的返回值是被忽略的导坟,如果要處理這種情況屿良,可以選擇自己實(shí)現(xiàn)該類,判斷如果返回值為false惫周,就拋出異常尘惧,然后在方法調(diào)用處去處理該異常。
修改后的代碼如下:
boolean res = mRemote.transact(Stub.TRANSACTION_queryNumberInfoAsync, _data, _reply, 0);
// If mRemote.transact() return false indicates that the method we call is not exist
// in server, so we need to throw an exception to notify the caller.
if (!res) {
throw new RemoteNoSuchMethodException("The method you are calling queryWeather() is not exist in server!");
}
這里的異常是繼承自RuntimeException的自定義異常
上面是對Binder流程的簡單分析递递,如果對AIDL原理不熟悉可以參考我的這篇博客
http://www.reibang.com/p/a9bf2e513751