Binder 架構設計
Binder 被設計出來是解決 Android IPC(進程間通信) 問題的。Binder 將兩個進程間交互的理解為 Client 向 Server 進行通信。
如下:binder總體架構圖
?
如上圖所示,Binder 架構分為 Client、Server、Service Manager 和 Binder Driver。
- Client: 服務調用者,一般就是我們應用開發者,通過調用諸如
List<PackageInfo> packs = getActivity().getPackageManager().getInstalledPackages(0);
?這樣的代碼,來向 ServerManager 請求 Package 服務。 - Server: 服務提供者,這里面會有許多我們常用的服務,例如 ActivityService 、 WindowMananger, 這些系統服務提供的功能,是的我們能夠使用 Wifi,Display等等設備,從而完成我們的需求。
- Service Manager: 這里是類似于前文中的DNS,絕大多數的服務都是通過 Service Manager來獲取,通過這個 DNS 來屏蔽掉 對其他Server的直接操作。
- Binder Driver: 底層的支持邏輯,在這里承擔路由的工作,不論風雨,使命必達,即使對面的server掛掉了,也會給你相應的死亡通知單 (Death Notification)
總結起來說,應用程序(Client) 首先向 Service Manager 發送請求 WindowManager 的服務,Service Manager 查看已經注冊在里面的服務的列表,找到相應的服務后,通過 Binder kernel 將其中的 Binder 對象返回給客戶端,從而完成對服務的請求。
Binder Driver 是怎樣充當路由角色的?
對于有網絡編程經驗的人來說,Socket?是很常用的概念。在Linux系統中,一切都被認為是文件,網絡流也是文件,同樣 Socket 也是文件,遵循著?open - write / read - close
的模式,Binder Framework在設計的時候,也同樣設計了類似的概念。
而在 Binder Framework 中 Binder 充當了 Socket 的角色,在不同的進程里面穿梭,提供了通信的基礎。對Binder而言,Binder可以看成Server提供的實現某個特定服務的訪問接入點, Client通過這個『地址』向Server發送請求來使用該服務;對Client而言,Binder可以看成是通向Server的管道入口,要想和某個Server通信首先必須建立這個管道并獲得管道入口。我們知道如果要訪問一個對象的話,需要拿到這個對象的引用地址,我們可以這么認為 Binder 就是遠程對象的一個地址,通過這個 Binder 就能輕松地拿到遠程對象的控制權,也可以說 Binder 是句柄,可能符合現在的場景。
而讓 Binder 起到上訴神奇作用的就是 Binder Driver。Binder Driver 在這里的作用就是前面提及的路由器,它工作在內核態,通過一系列?open()
?,?mmap()
,?ioctl()
?,?poll()
?等操作,指定了一系列的協議,實現了 Binder 在不同進程之間的傳遞工作,這里就不再詳細闡述了,有興趣的同學可以自行查看相關文檔。
Service Manager 怎么當DNS的?
根據前文的描述,Service Manager是將相應的服務名字轉換成具體的引用,也就是說使得 client 能夠通過 bidner 名字來從 Server 中拿到對 binder 實體的引用。這里唯一需要特別說明的地方在于,Service Manager 的特殊性。我們知道 Service Manager 是一個進程,其他 Server 也是另一個進程,他們之間是如何進行通信的了?在沒有其他中間服務進程的參與下,Service Manager 與 其他進程如何憑空通信?
這就是先有雞,還是先有蛋的問題。答案是先有雞,也就是說 Service Manager 首先就被創建了,并被賦予了一個特殊的句柄,這個句柄就是 0 。換而言之,其他 Server 進程都可以通過這個?0句柄
?與 Service Manager 進行通信,在整個系統啟動時,其他 Server 進程都向這個?0句柄
?進行注冊,從而使得客戶端進程在需要調用服務時,能夠通過這個 Service Manager 查詢到相應的服務進程。
?
如下:binder framework 工作原理圖
?
理解Aidl中Stub和Stub.Proxy
aidl生成的java代碼中,Stub類是繼承于Binder類的,也就是說Stub實例就是Binder實例。
服務端一般會實例化一個Binder對象,例如:
?
public class AIDLService extends Service { private static final String TAG = "AIDLService"; IPerson.Stub stub = new IPerson.Stub() { @Override public String greet(String someone) throws RemoteException { Log.i(TAG, "greet() called"); return "hello, " + someone; } }; @Override public IBinder onBind(Intent intent) { Log.i(TAG, "onBind() called"); return stub; } ... }
?
客戶端中在Service綁定的時候可以獲取到這個Stub(Binder),如:
private IPerson person; private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.i("ServiceConnection", "onServiceConnected() called"); person = IPerson.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { //This is called when the connection with the service has been unexpectedly disconnected, //that is, its process crashed. Because it is running in our same process, we should never see this happen. Log.i("ServiceConnection", "onServiceDisconnected() called"); } };
像上面一樣,在連接Service的時候,服務端的Stub(Binder)以參數的形式傳過來了–IBinder service,然后我們通過asInterface()方法獲取它的實例對象。
?
aidl文件自動生成的java類中可以看到asInterface()這個接口的實現,大概的意思就是:?
如果客戶端和服務端在同一個進程下,那么asInterface()將返回Stub對象本身,否則返回Stub.Proxy對象。
也就是說asInterface()返回的對象有兩種可能(實際上有三種,還有一種是null),Stub和Stub.Proxy。它們有什么區別呢?
-
如果在同一個進程下的話,那么asInterface()將返回服務端的Stub對象本身,因為此時根本不需要跨進稱通信,那么直接調用Stub對象的接口就可以了,返回的實現就是服務端的Stub實現,也就是根本沒有跨進程通信;
-
如果不是同一個進程,那么asInterface()返回是Stub.Proxy對象,該對象持有著遠程的Binder引用,因為現在需要跨進程通信,所以如果調用Stub.Proxy的接口的話,那么它們都將是IPC調用,它會通過調用transact方法去與服務端通信。
以上就是兩者的區別。
Stub是服務端實現的存根,而Proxy則是Stub的代理。?
?
public interface IPerson extends android.os.IInterface { public static abstract class Stub extends android.os.Binder implements IPerson { public Stub(){this.attachInterface(this, DESCRIPTOR);}...public static IPerson asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; }
//mDescriptor的初始化在attachInterface()過程中賦值android.os.IInterface iin = (android.os.IInterface) obj.queryLocalInterface(DESCRIPTOR); //查詢本地 if (((iin != null) && (iin instanceof IPerson))) { return ((IPerson) iin); } return new IPerson.Stub.Proxy(obj); } ... @Overridepublic boolean onTransact(int code, android.os.Parcel data,android.os.Parcel reply, int flags)throws android.os.RemoteException {switch (code) {case INTERFACE_TRANSACTION: {reply.writeString(DESCRIPTOR);return true;}case TRANSACTION_greet: {data.enforceInterface(DESCRIPTOR);java.lang.String _arg0;_arg0 = data.readString();java.lang.String _result = this.greet(_arg0);reply.writeNoException();reply.writeString(_result);return true;}}return super.onTransact(code, data, reply, flags);}private static class Proxy implements IPerson {private android.os.IBinder mRemote;Proxy(android.os.IBinder remote) {mRemote = remote;}@Overridepublic android.os.IBinder asBinder() {return mRemote;}public java.lang.String getInterfaceDescriptor() {return DESCRIPTOR;}@Overridepublic java.lang.String greet(java.lang.String someone)throws android.os.RemoteException {android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();java.lang.String _result;try {_data.writeInterfaceToken(DESCRIPTOR);_data.writeString(someone);mRemote.transact(Stub.TRANSACTION_greet, _data, _reply, 0);_reply.readException();_result = _reply.readString();} finally {_reply.recycle();_data.recycle();}return _result;}}static final int TRANSACTION_greet = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); //方法用數字表示 } }
Binder機制框架概覽
?如何從進程A傳兩個整數給進程B,進程B把兩個數相加后返回結果給進程A。
下面我們從總體上看一看這個方案是怎樣設計的:
進程A通過bindService
方法去綁定在進程B中注冊的一個service
,系統收到進程A的bindService
請求后,會調用進程B中相應service
的onBind
方法,該方法返回一個特殊對象,系統會接收到這個特殊對象,然后為這個特殊對象生成一個代理對象,再將這個代理對象返回給進程A,進程A在ServiceConnection
回調的onServiceConnected
方法中接收該代理對象,依靠這個代理對象的幫助,就可以解決我們的問題啦。
?總體流程如下圖:
?
?
step 1: 進程B創建Binder 對象
為進程B實現一個特殊的對象,就是前面提到的service
的onBind
方法要返回的對象。這個對象有兩個特性:
- 一個是具有完成特定任務的能力(在我們的問題中,就是將兩個整數相加并返回結果的能力)
- 一個是被跨進程傳輸的能力。
什么樣的對象具有這樣的能力呢?答案是Binder
類的對象。下面我們分析一下Binder
是怎樣擁有這兩個能力的。
Binder中有如下關鍵方法:
public class AIDLService extends Service { private static final String TAG = "AIDLService"; IPerson.Stub stub = new IPerson.Stub() { @Override public String greet(String someone) throws RemoteException { Log.i(TAG, "greet() called"); return "hello, " + someone; } }; @Override public IBinder onBind(Intent intent) { Log.i(TAG, "onBind() called"); return stub; } ... }
Binder具有被跨進程傳輸的能力是因為它實現了IBinder
接口。系統會為每個實現了該接口的對象提供跨進程傳輸,這是系統給我們的一個很大的福利。
Binder具有的完成特定任務的能力是通過它的attachInterface
方法獲得的,我們可以簡單理解為該方法會將(descriptor,plus
)作為(key,value
)對存入Binder
對象中的一個Map<String,IInterface>
對象中,Binder
對象可通過attachInterface
方法持有一個IInterface
對象(即plus
)的引用,并依靠它獲得完成特定任務的能力。queryLocalInterface
方法可以認為是根據key
值(即參數?descriptor
)查找相應的IInterface
對象。onTransact
方法暫時不用管,后面會講到。
好的,現在我們來實現IInterface
和Binder
對象,概略代碼如下:
public interface IPlus extends IInterface {public int add(int a,int b); }public class Stub extends Binder {@Overrideboolean onTransact(int code, Parcel data, Parcel reply, int flags){......//這里我們覆寫了onTransact方法,暫時不用管,后面會講解。 }...... } IInterface plus = new IPlus(){//匿名內部類public int add(int a,int b){//定制我們自己的相加方法return a+b;}public IBinder asBinder(){ //實現IInterface中唯一的方法,return null ;} }; Binder binder = new Stub(); binder.attachIInterface(plus,"PLUS TWO INT");
step 2: 進程A接收進程B的Binder對象
好了,現在我們有了這個特殊的對象binder
,可以在進程B的service
中的onBind
方法將它返回了,即return binder ;
下面就是見證奇跡的時候。系統會首先收到這個binder
對象,然后,它會生成一個BinderProxy
(就是前面提到的Binder 的內部類)類的對象,姑且稱之為binderproxy
,然后將該對象返回給進程A,現在進程A終于在onServiceConnected
方法中接收到了binderproxy
對象(心情有木有小激動?)。為了下面講解方便,再次貼出Binder
類的概要信息。
public class Binder implement IBinder{void attachInterface(IInterface plus, String descriptor)IInterface queryLocalInterface(Stringdescriptor) //從IBinder中繼承而來boolean onTransact(int code, Parcel data, Parcel reply, int flags)//暫時不用管,后面會講。final class BinderProxy implements IBinder {IInterface queryLocalInterface(Stringdescriptor) {return null ;//注意這行代碼!!//下面會講到。這行代碼只是示例,不是源代碼。 }......} }
此時的進程A以為收到的是binder
對象,它興奮了,它迫不及待地要通過queryLocalInterface
方法獲取這個binder
的plus
對象,利用該對象的加法功能進行加法計算。可結果呢?
首先,binderproxy.queryLocalInterface("PLUS TWO INT")
調用是合法的,因為queryLocalInterface
方法是IBinder
中的方法,而BinderProxy
和Binder
都實現了IBinder
接口。但是,binderproxy
對象顯然沒有plus
對象,因為它根本就沒有attachInterface
方法(這是Binder
才有滴)。所以,可想而知,進程A的binderproxy.queryLocalInterface("PLUS TWO INT")
調用返回的將是一個null
(參見上面的示例代碼)。
step 3: 進程A利用進程B傳過來的對象發起請求
進程A出離憤怒了,我要的是binder
,我要的是它里面的plus
來幫我完成加法運算,進程B竟然給我一個冒牌貨binderproxy
(顯然,它冤枉了進程B,都是系統惹得禍)。
正在進程A氣得頭頂冒煙時,binderproxy
對象說話了:“別生氣進程A,我雖然只是binder
對象的代理,但是,我也不是吃素的,你把你的數據(兩個int
)和你想進行的操作(plus.add
)通過我的transact
方法(這是在IBinder
接口中定義的方法)交給我,我可以替你向binder
對象請求你需要的功能,等binder
對象把結果給我時,我再把結果交給你不就行了?”
于是,進程A通過binderproxy
對象的transact
方法,提交了請求。代碼概略如下:
android.os.Parcel data = android.os.Parcel.obtain(); android.os.Parcel reply = android.os.Parcel.obtain();int _result;data.writeInterfaceToken("PLUS TWO INT"); data.writeInt(a); data.writeInt(b); binderproxy.transact(1, data, reply, 0);//為簡單起見,最后一個0暫時不管它
簡單解釋一下上面代碼。data
是用來寫進程A的數據的(即整數 a和b),reply
是準備用來接收結果的。transact
方法中的第一個參數是整數1,它是進程A與進程B的一個約定,1就代表想讓進程B對進程A傳入的數據執行加法操作。這個約定也可以定義在 Stub類中,如下所示:public static final int ADD = 1;
此時,我們可以將binderproxy.transact(1, data, reply, 0);
中的1替換為Stub.ADD
。Stub.ADD
其實可以是任何整數值的,我們選擇1純屬為了簡單。
step 4: 進程B收到并處理進程A的請求
binderproxy.transact
調用發生后,會引起系統的注意,系統意識到binderproxy
想找它的真身binder
對象執行一個操作了(看!系統其實一直存著binder
和binderproxy
的對應關系呢!)。于是系統將這個請求中的數據轉發給binder
對象,binder
對象將會在onTransact
中收到binderproxy
傳來的數據(Stub.ADD,data,reply,0
),于是它從data
中取出進程A傳來的數據,又根據Stub.ADD
確定進程A想讓它執行加法操作,于是它就執行了加法操作,并把結果寫回reply
。代碼概略如下:
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR);return true; } //樣板代碼,不用管,下一行才是重點。case Stub.ADD: { data.enforceInterface("PLUS TWO INT"); int _arg0; _arg0 = data.readInt();int _arg1; _arg1 = data.readInt();int _result = this.queryLocalIInterface("PLUS TWO INT") .add(_arg0, _arg1); reply.writeNoException(); reply.writeInt(_result); return true; }} return super.onTransact(code, data, reply, flags); }
簡單解釋一下以上代碼。我們知道進程A寫數據時寫入了一個InterfaceToken
,就是這行代碼data.writeInterfaceToken("PLUS TWO INT");
這個意思是說,讓進程B在自己的binder
對象中利用PLUS TWO INT
調用queryLocalIInterface
方法查找相應的IInterface
對象,進程A要執行的操作就在該對象中,至此,我們很容易理解Stub.ADD
就代表了plus
中的add
方法。這是一個二級查找過程,即通過PLUS TWO INT
確定要plus
來執行功能,通過Stub.ADD
確定要執行plus
中的add
方法。
step 5: 進程A獲取進程B返回的處理結果
進程B把結果寫入reply
后,進程A就可以從reply
讀取結果了。代碼概略如下:
binderproxy.transact(Stub.ADD, data, reply, 0);
reply.readException();
_result = reply.readInt();
?
?更深入的Binder原理參考
Android Bander設計與實現 - 設計篇