1. 程式人生 > >android Binder機制

android Binder機制

pro end abs close 概念 exp 是的 一切都 實例化

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。它們有什麽區別呢?

  1. 如果在同一個進程下的話,那麽asInterface()將返回服務端的Stub對象本身,因為此時根本不需要跨進稱通信,那麽直接調用Stub對象的接口就可以了,返回的實現就是服務端的Stub實現,也就是根本沒有跨進程通信;

  2. 如果不是同一個進程,那麽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中相應serviceonBind方法,該方法返回一個特殊對象,系統會接收到這個特殊對象,然後為這個特殊對象生成一個代理對象,再將這個代理對象返回給進程A,進程A在ServiceConnection回調的onServiceConnected方法中接收該代理對象,依靠這個代理對象的幫助,就可以解決我們的問題啦。

總體流程如下圖:

技術分享

step 1: 進程B創建Binder 對象

為進程B實現一個特殊的對象,就是前面提到的serviceonBind方法要返回的對象。這個對象有兩個特性:

  • 一個是具有完成特定任務的能力(在我們的問題中,就是將兩個整數相加並返回結果的能力)
  • 一個是被跨進程傳輸的能力。

什麽樣的對象具有這樣的能力呢?答案是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方法暫時不用管,後面會講到。

好的,現在我們來實現IInterfaceBinder對象,概略代碼如下:

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方法獲取這個binderplus對象,利用該對象的加法功能進行加法計算。可結果呢?
首先,binderproxy.queryLocalInterface("PLUS TWO INT")調用是合法的,因為queryLocalInterface方法是IBinder中的方法,而BinderProxyBinder都實現了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.ADDStub.ADD其實可以是任何整數值的,我們選擇1純屬為了簡單。

step 4: 進程B收到並處理進程A的請求

binderproxy.transact調用發生後,會引起系統的註意,系統意識到binderproxy想找它的真身binder對象執行一個操作了(看!系統其實一直存著binderbinderproxy的對應關系呢!)。於是系統將這個請求中的數據轉發給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機制