IPC機(jī)制:AIDL調(diào)用流程分析和常見問題處理

當(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;
}
  1. 首先初始化兩個(gè)parcel:傳入?yún)?shù)_data和返回參數(shù)_reply,
    將方法中所有的參數(shù)都寫入_data中嚼酝,普通對象調(diào)用的是它的writeToParcel方法浮还,
    如果參數(shù)是一個(gè)aidl接口即繼承了IInterface接口,則調(diào)用的是Parcel.writeStrongBinder方法闽巩。

  2. 將所有參數(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é)束了。


上面的流程可以用下圖來說明:

Binder流程.png


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)致的問題昌腰?

  1. 在序列化時(shí)开伏,可以通過手動setDataPosition(),通過控制position 將新增字段寫進(jìn)固定的沒有使用過的位置遭商,從而使其不影響反序列化
  2. 在寫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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末喷橙,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子登舞,更是在濱河造成了極大的恐慌贰逾,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件菠秒,死亡現(xiàn)場離奇詭異疙剑,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門核芽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來囚戚,“玉大人,你說我怎么就攤上這事轧简〕鄯唬” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵哮独,是天一觀的道長拳芙。 經(jīng)常有香客問我,道長皮璧,這世上最難降的妖魔是什么舟扎? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮悴务,結(jié)果婚禮上睹限,老公的妹妹穿的比我還像新娘。我一直安慰自己讯檐,他們只是感情好羡疗,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著别洪,像睡著了一般叨恨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上挖垛,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天痒钝,我揣著相機(jī)與錄音,去河邊找鬼痢毒。 笑死送矩,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的闸准。 我是一名探鬼主播益愈,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼夷家!你這毒婦竟也來了蒸其?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤库快,失蹤者是張志新(化名)和其女友劉穎摸袁,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體义屏,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡靠汁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年蜂大,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蝶怔。...
    茶點(diǎn)故事閱讀 39,711評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡奶浦,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出踢星,到底是詐尸還是另有隱情澳叉,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布沐悦,位于F島的核電站成洗,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏藏否。R本人自食惡果不足惜瓶殃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望副签。 院中可真熱鬧遥椿,春花似錦、人聲如沸淆储。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽遏考。三九已至,卻和暖如春蓝谨,著一層夾襖步出監(jiān)牢的瞬間灌具,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工譬巫, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留咖楣,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓芦昔,卻偏偏與公主長得像诱贿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子咕缎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評論 2 353