android Binder機制
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; } 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); } ... @Override 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 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; } @Override public android.os.IBinder asBinder() { return mRemote; } public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } @Override public 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 { @Override boolean 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設計與實現 - 設計篇
android Binder機制