Binder:為什么要通過onTransact()調(diào)用目標(biāo)方法

0x00 背景

最近被提出一串問題:為什么android.os.Binder要提供onTransact()方法給子類重寫。為什么要通過Client:invokeMethod -> onTransact() -> Service:targetMethod這一曲折過程來調(diào)用一個(gè)遠(yuǎn)程方法,為什么不能直接指定方法名稱來調(diào)用。

這些問題闡述了同一個(gè)疑問:對(duì)于調(diào)用者而言,目標(biāo)方法為何遠(yuǎn)在天邊。

實(shí)際上,這并不是一個(gè)無關(guān)緊要的問題。嘗試解答此問題有助于了解API設(shè)計(jì)者的初衷,以便從不同角度探究可能潛在的問題。了解系統(tǒng)創(chuàng)作者的思想,從而更好地使用其設(shè)計(jì)的接口及系統(tǒng)特性,避免出現(xiàn)錯(cuò)誤決策導(dǎo)致長期迭代中難以維護(hù)。

本文假設(shè)你已具備IPC(進(jìn)程間通信)開發(fā)經(jīng)驗(yàn)。

0x01 AIDL 與 onTransact()

做一些簡單回顧:

首選,無論定義于何處,.aidl文件始終是生成目標(biāo).java文件的聲明標(biāo)記。大多數(shù)情況下,對(duì)android.os.IInterfaceandroid.os.Binder的繼承及實(shí)現(xiàn)不應(yīng)當(dāng)手動(dòng)操作。所有的生成行為應(yīng)當(dāng)由.aidl聲明來完成。此外,所生成的目標(biāo)文件位于app/build/generated/source目錄下。

其次,android.os.Binder實(shí)現(xiàn)于android.os.IBinder。android.os.Binder實(shí)現(xiàn)了大多數(shù)進(jìn)程狀態(tài)所必要的功能,以及所有必要的功能調(diào)度。

.aidl存在的目的就是為了剔除大量重復(fù)性的工作。這其中包括,所生成目標(biāo)文件下的類種類Stub方法:onTransact(int, android.os.Parcel, android.os.Parcel, int)。此方法重寫自android.os.Binder。此外,.aidl是為對(duì)外暴露接口而設(shè)計(jì)的。

0x02 onTransact() 干了些什么

Binder.onTransact()是為Binder.transact()的調(diào)用而準(zhǔn)備的。Binder.transact()做了兩件事:

public final boolean transact(int code, Parcel data, Parcel reply,
        int flags) throws RemoteException {
    if (data != null) {
        data.setDataPosition(0);
    }
    boolean r = onTransact(code, data, reply, flags);
    if (reply != null) {
        reply.setDataPosition(0);
    }
    return r;
}

此段代碼位于android/os/Binder.java

首先,在調(diào)用Binder.onTransact()之前及之后,分別對(duì)請(qǐng)求結(jié)構(gòu)的引用及返回結(jié)構(gòu)的引用重置讀寫position,以及調(diào)用Binder.onTransact()。在此提醒,Binder.transact()的調(diào)用者是Stub下的內(nèi)部類Proxy中的各個(gè).aidl中定義的方法。

最終,千辛萬苦地,終于來到了.aidl自行生成實(shí)現(xiàn)的Binder.onTransact()方法了。特別的是,有兩個(gè)地方值得去注意:

  • 其一,在最終的開發(fā)中,將會(huì)繼承抽象類Stub,并實(shí)現(xiàn)所有在.aidl中定義的方法。這些具體方法的直接調(diào)用者,正是當(dāng)前我們所在的onTransact()方法;

  • 其二,正是基于上面一條,可以得知:無論遠(yuǎn)程調(diào)用者(Client)身處何方,最終,一定會(huì)經(jīng)過此處的onTransact()方法,并由onTransact()直接調(diào)用目標(biāo)方法。

要知道,傳入onTransact()方法的參數(shù)中,擁有目標(biāo)方法的ID、指向參數(shù)的引用,以及指向返回結(jié)果的引用。所有遠(yuǎn)程調(diào)用者(Client)想要做的事,都通過層層調(diào)用及參數(shù)包裝匯聚到onTransact(),再由onTransact()分發(fā)到真正的目標(biāo)方法執(zhí)行。

那么問題來了。為什么?

0x03 關(guān)鍵問題:進(jìn)程隔離

一個(gè)簡潔明了的回答是:除了預(yù)先定義的接口,其余的一切實(shí)現(xiàn)在進(jìn)程間均相互不可見。

現(xiàn)在,以更直觀的方式來展示調(diào)用者(Client)與服務(wù)(Service)間的關(guān)系:

上下層關(guān)系

顯然,所有的Client亦或是Service,都是平級(jí)的。原因顯而易見:這部分運(yùn)行時(shí)程序,都是基于Android Framework開發(fā)的。

那么,如果A程序自行定義了接口,B程序怎樣知道A程序定義了接口?換言之:B程序該通過什么方式來查找A程序中的自定義接口以至調(diào)用?顯然,所有基于Android Framework開發(fā)的程序,都不存在上下級(jí)關(guān)系。

同時(shí),由于虛擬機(jī)相互獨(dú)立,因此這些程序并不在同一個(gè)運(yùn)行時(shí)中。兩兩之間相隔一堵不透明的墻,它們唯一可見的,就是下層的Binder元素。

答案就此基本浮出水面。Client與Service的狀態(tài)是不可預(yù)知的,使用Binder Driver隱藏進(jìn)程間調(diào)用細(xì)節(jié),并通過Binder.onTransact()分發(fā)調(diào)用指令,最終在參數(shù)引用中寫入計(jì)算結(jié)果——這一過程實(shí)現(xiàn)了設(shè)計(jì)模式中的簡易命令模式。整個(gè)進(jìn)程間調(diào)用作用于Binder Driver,至于Binder.onTransact()格外引人矚目,則是因?yàn)樗钦麄€(gè)過程的末端操作。

正如上圖所示,把所有請(qǐng)求匯聚到onTransact(),具體需要請(qǐng)求哪個(gè)方法,則抽象為id處理。另外,所有目標(biāo)方法的請(qǐng)求參數(shù)及返回體都要求是基本類型或被.aidl所定義的。這意味著在傳輸過程中所有信息都被視作“流”來處理。

0x04 更多思考

理解Binder調(diào)度過程有助于設(shè)計(jì)更易于維護(hù)的接口——尤其是庫。某些需求的實(shí)現(xiàn)可能需要對(duì)ProxyStub進(jìn)行手動(dòng)編輯,此時(shí)理解API設(shè)計(jì)者的意圖顯得極為重要。

畢竟,維護(hù)那些憑直覺寫出的代碼,簡直就是災(zāi)難。

內(nèi)推

現(xiàn)在,歡聚時(shí)代(YY Inc.)及虎牙(HUYA Inc.)所有崗位(包括但不限于 Android、iOS、Java、前端、大數(shù)據(jù)、機(jī)器學(xué)習(xí)、音視頻算法、其他非技術(shù)崗均可)均可進(jìn)行內(nèi)部推薦。

發(fā)送簡歷到 zhujiajun#yy.com(#替換成@),并附上簡歷,即可內(nèi)推。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容