*本篇文章已授權微信公衆号 guolin_blog (郭霖)獨家釋出
文章目錄
-
- 前言
- 概要
- 解剖
-
- asInterface
- asBinder
- IInterface
- DESCRIPTOR
- onTransact(服務端接收)
- transact(用戶端調用)
- 總結
前言
繼上一篇AIDL的簡單介紹,相信應該對AIDL有一個大緻的了解,那麼這一篇我們來深入探讨一下AIDL為什麼能夠完成這個跨程序操作,這其中是否隐藏着一些不為人知的秘密,讓我們跟着筆者的思路,慢慢撥開籠罩在AIDL上的謎團。
概要
先用上圖整體描述這個AIDL從用戶端(Client)發起請求至服務端(Server)相應的工作流程,我們可以看出整體的核心就是Binder
解剖
asInterface
用于将服務端的Binder對象轉換成用戶端所需的AIDL接口類型的對象,這種轉換過程是區分程序的【如果用戶端和服務端位于同一程序,那麼此方法傳回的就是服務端的Stub對象本身,否則傳回的是系統封裝後的Stub.proxy對象】
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IDemandManager demandManager = IDemandManager.Stub.asInterface(service);
}
};
在
ServiceConnection
綁定服務的方法裡,我們通過
IDemandManager.Stub.asInterface(service)
方法獲得
IDemandManager
對象,我們跟着
asInterface
步伐看看裡面有什麼名堂。
PS :
onServiceConnected
方法的(IBinder)service參數在ActivityThread建立,他們之間還會扯出ActivityManagerService,ManagerService等,有興趣的可以通過Activity 的啟動過程加深功力,深入了解。
從上圖的類結構圖中我們可以看出,這個IDemandManager.aidl檔案通過編譯成為一個接口類,而這個類最核心的成員是Stub類和Stub的内部代理類Proxy。
順着
asInterface
方法,結合上面對該方法的描述,可以看出通過
DESCRIPTOR
辨別判斷
如果是同一程序,那麼就傳回Stub對象本身(
obj.queryLocalInterface(DESCRIPTOR)
),否則如果是跨程序則傳回Stub的代理内部類Proxy。
也就是說這個
asInterface
方法傳回的是一個遠端接口具備的能力(有什麼方法可以調用),在我們項目裡,
asInterface
的能力就是get/setDemand和注冊/解綁監聽接口。
asBinder
緊接着
asInterface
,我們看到一個簡潔的方法
asBinder
顧名思義,asBinder用于傳回目前Binder對象。
//Stub
@Override
public android.os.IBinder asBinder() {
return this;
}
//Proxy
private android.os.IBinder mRemote;
@Override
public android.os.IBinder asBinder() {
return mRemote;
}
根據代碼我們可以追溯到,proxy的這個mRemote就是綁定服務(bindService)時候
由
IDemandManager.Stub.asInterface(binder);
傳入的
IBinder
對象。
這個Binder對象具有跨程序能力,在Stub類裡面(也就是本程序)直接就是Binder本地對象,在Proxy類裡面傳回的是遠端代理對象(Binder代理對象)。[是以跨程序的謎團,會随着對Binder的分析和研究,逐漸變得清晰起來。]
因為我們這個編譯生成的
IDemandManager
接口繼承了
android.os.IInterface
接口,是以我們先分析
IInterface
接口。
IInterface
而這個
IInterface
接口就隻聲明了一個方法,但是Stub和Proxy都分别間接的實作了該接口。
/**
* Base class for Binder interfaces. When defining a new interface,
* you must derive it from IInterface.
*/
public interface IInterface
{
/**
* Retrieve the Binder object associated with this interface.
* You must use this instead of a plain cast, so that proxy objects
* can return the correct result.
*/
public IBinder asBinder();
}
Binder接口的基類。 定義新接口時,
你必須實作IInterface接口。
檢索與此界面關聯的Binder對象。
你必須使用它而不是一個簡單的轉換
這樣代理對象才可以傳回正确的結果。
從上面的系統注釋中我們可以了解出:
- 要聲明(或者是手動diy建立)AIDL性質的接口,就要繼承
IInterface
- 代表遠端server對象具有的能力,具體是由
表達出這個能力。Binder
DESCRIPTOR
Binder的唯一辨別,一般用目前Binder的類名表示。
onTransact(服務端接收)
我們發現
IDemandManager
接口,實際上并沒有太多複雜的方法,看完了
asInterface
和
asBinder
方法,我們再來分析
onTransact
方法。
onTransact
方法運作在服務端中的Binder線程池中
用戶端發起跨程序請求時,遠端請求會通過系統底層封裝後交給此方法來處理。
如果此方法傳回false,那麼用戶端的請求就會失敗。
- code : 确定用戶端請求的目标方法是什麼。(項目中的getDemand或者是setDemandIn方法)
- data : 如果目标方法有參數的話,就從data取出目标方法所需的參數。
- reply : 當目标方法執行完畢後,如果目标方法有傳回值,就向reply中寫入傳回值。
- flag : Additional operation flags. Either 0 for a normal RPC, or FLAG_ONEWAY for a one-way RPC.(暫時還沒有發現用處,先标記上英文注釋)
也就是說,這個
onTransact
方法就是服務端處理的核心,接收到用戶端的請求,并且通過用戶端攜帶的參數,執行完服務端的方法,傳回結果。下面通過系統生成的代碼,我們簡要的分析一下
onTransact
方法裡我們項目寫的
setDemandIn
和
setDemandOut
方法。
case TRANSACTION_setDemandIn: {
data.enforceInterface(DESCRIPTOR);
qdx.aidlserver.MessageBean _arg0;
if ((0 != data.readInt())) {
_arg0 = qdx.aidlserver.MessageBean.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.setDemandIn(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_setDemandOut: {
data.enforceInterface(DESCRIPTOR);
qdx.aidlserver.MessageBean _arg0;
_arg0 = new qdx.aidlserver.MessageBean();
this.setDemandOut(_arg0);
reply.writeNoException();
if ((_arg0 != null)) {
reply.writeInt(1);
_arg0.writeToParcel(reply,android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else {
reply.writeInt(0);
}
return true;
}
此段代碼并沒有多少玄機,就是負責資料的讀寫,以及結果的傳回。
但是有一個知識點可以在此驗證,就是定向tag的作用,上面的方法定向tag分别是in和out,上篇文章介紹定向tag的作用就是跨程序中資料的流向,
setDemandIn
方法中我們可以看到我們讀取了用戶端傳遞過來的資料參數data,即用戶端->服務端。out方法可以同理自行分析。AIDL中的in,out,inout分析
transact(用戶端調用)
分析完了Stub,就剩下Stub的内部代理類Proxy
驚奇的發現Proxy類主要是用來方法調用,也就是用來用戶端的跨程序調用。
transact
方法運作在用戶端,首先它建立該方法所需要的輸入型Parcel對象_data、輸出型Parcel對象_reply;
接着調用綁定服務傳來的IBinder對象的transact方法來發起遠端過程調用(RPC)請求,同時目前線程挂起;
然後服務端的
方法會被調用,直到RPC過程傳回後,目前線程繼續執行,并從_reply中取出RPC過程的傳回結果,也就是傳回_reply中的資料。
onTransact
我們看到獲得
Parcel
的方法為
Parcel.obtain()
,按照套路這個應該就是從
Parcel
池中擷取該對象,減少建立對象的開支,跟進方法我們可以看得的确是建立了一個
POOL_SIZE
為6的池用來擷取
Parcel
對象。
是以在跨程序通訊中Parcel是通訊的基本單元,傳遞載體。
而這個
transact
方法是一個本地方法,在native層中實作,功力不足,點到為止。
分析到了這裡,感覺頓悟了許多,再一次引用開頭概述的圖檔來看大概,整體的思路便浮在腦海中。so 哒死内~
總結
通過對AIDL的分析,我們發現原來這一切圍繞着Binder有序的展開
AIDL通過Stub類用來接收并處理資料,Proxy代理類用來發送資料,而這兩個類也隻是通過對Binder的處理和調用,下一篇我們将深入摸清這個Binder究竟為何物,能夠輕松遊走于跨程序之中。
是以所謂的服務端和用戶端,我們拆開看之後發現原來竟是不同類的處理
Stub充當服務端角色,持有Binder實體(本地對象)。
- 擷取用戶端傳過來的資料,根據方法 ID 執行相應操作。
- 将傳過來的資料取出來,調用本地寫好的對應方法。
- 将需要回傳的資料寫入 reply 流,傳回用戶端。
Proxy代理類充當用戶端角色,持有Binder引用(句柄)。
- 生成 _data 和 _reply 資料流,并向 _data 中存入用戶端的資料。
- 通過 transact() 方法将它們傳遞給服務端,并請求服務端調用指定方法。
- 接收 _reply 資料流,并從中取出服務端傳回來的資料。
而且所謂的服務端和用戶端都是相對而言的,服務端不僅可以接收和處理消息,而且可以定時往用戶端發送資料,與此同時服務端使用Proxy類跨程序調用,相當于充當了"Client"。
并且有一點要了解是,跨程序通訊的時候,傳遞的資料對象并不是從程序A原原本本的發給程序B。庫克說要送你蘋果,而你收到的iphone X就真的是美國生産的嗎?不,它也可能是made in China.
終上所述,AIDl這個工具就已經分析結束,如果文中有不足之處還望指出。如果有什麼更好的見解也可以留言,最終的目标都是共同進步。有興趣的可以繼續看Android Binder之應用層總結與分析。
AIDL文章借鑒(上)
AIDL文章借鑒(下)
最後感謝《Android 開發藝術探索》專業的分析。