帶你一起剖析Android AIDL跨程序通訊的實現原理
今日科技快訊
從8月份釋出混改方案開始,中國聯通作為首批混改試點中惟一一家集團層面整體混改的央企,也是全球首例“電信運營商+網際網路巨頭”戰略融合先行試水者,一直倍受關注。而就在昨日,有業內人士稱:中國人壽、騰訊、百度、京東、阿里、蘇寧等戰略投資者,已經完成了750億元真金白銀的投入,彰顯了對聯通混改前景的信心。
作者簡介明天就是週末啦,提前祝大家週末愉快!
本篇依舊來自 凶殘的程式設計師 的投稿,繼上一篇《Android跨程序通訊,深入淺出AIDL》的進階篇,今天帶領大家深入瞭解AIDL,希望大家喜歡!
凶殘的程式設計師 的部落格地址:
前言http://blog.csdn.net/qian520ao
今天我們來深入探討一下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的啟動過程
http://blog.csdn.net/qian520ao/article/details/78156214
從上圖的類結構圖中我們可以看出,這個 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介面。
public interface IDemandManager extends android.os.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物件。你必須使用它而不是一個簡單的轉換,這樣代理物件才可以返回正確的結果。
從上面的系統註釋中我們可以理解出:
1. 要宣告(或者是手動diy建立)AIDL性質的介面,就要繼承 IInterface
2. 代表 遠端server物件 具有的能力,具體是由 Binder 表達出這個能力。
DESCRIPTOR
//系統生成的
private static final java.lang.String DESCRIPTOR = "qdx.aidlserver.IDemandManager";
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分析
http://www.jianshu.com/p/ddbb40c7a251
transact(客戶端呼叫)
分析完了 Stub,就剩下 Stub 的內部代理類 Proxy
驚奇的發現 Proxy類 主要是用來方法呼叫,也就是用來客戶端的跨程序呼叫。
transact方法 執行在客戶端,首先它建立該方法所需要的 輸入型Parcel物件 _data、輸出型Parcel物件 _reply;
接著呼叫繫結服務傳來的 IBinder物件 的 transact方法 來發起遠端過程呼叫(RPC)請求,同時當前執行緒掛起;
然後服務端的 onTransact方法 會被呼叫,直到 RPC 過程返回後,當前執行緒繼續執行,並從 _reply 中取出 RPC 過程的返回結果,也就是返回 _reply 中的資料。
我們看到獲得 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之應用層總結與分析
http://blog.csdn.net/qian520ao/article/details/78089877
歡迎長按下圖 -> 識別圖中二維碼
或者 掃一掃 關注我的公眾號