Binder使用

Binder

  • 在安卓使用Binder實現進程間通信需要做哪些工作
  • 如何模糊跨進程調用與進程內調用?
  • 如何使用AIDL

如何利用Binder實現進程間通信

我們先看下Binder調用大致原理,這是Binder調用的標準調用過程,我們下面的代碼將逐漸從不標準過程轉成標準過程:


Binder.png

首先,我們 android studio 新建兩個工程(兩個 moudle 也可以,這里目的為創建兩個運行在不同進程的app),一個Server,一個Client,而后,在Server中,我們新建一個java類Stub(類名無所謂),繼承android.os.Binder,之后重寫onTransact 方法,此處注意,onTransact方法的四個參數:

  • code:方法標識符,因為Client端對Server端的所有調用都會走到Server端的這個方法,所以理所應當Client端應該傳遞一個參數過來用以表示要調用哪個方法,注意這個int類型的標識必須介于 FIRST_CALL_TRANSACTION 和 LAST_CALL_TRANSACTION之間,所以我們給方法分配code的時候最好使用FIRST_CALL_TRANSACTION+n 這種方式
  • data :Client傳遞過來的序列化數據包,Parcel類型
  • reply: 如果Client端調用時需要返回值,Server通過這個對象將返回值傳遞回去,同樣Parcel類型
  • flag 用來區分這個調用是普通調用還是單邊調用,普通調用時,Client端線程會阻塞,直到從Server端接收到返回值(所以如果Client端是主線程調用,其調用的Server端不宜做耗時操作,這會讓造成Client的ANR),若flag==IBinder.FLAG_ONEWAY,則這次調用是單邊調用,Client在傳出數據后會立即執行下一段代碼,此時兩端異步執行,單邊調用時函數返回值必須為void (也就是異步調用必須舍棄返回值,要返回值就必須阻塞等待)

有以上,Server端的功能就已經可以實現,但在兩端通信時,為了兩端Binder匹配,我們還需要在Server端做一次驗證,用到data.enforceInterface(DESCRIPTOR)這個方法,DESCRIPTOR是Binder描述符,Binder Server和Client之間將通過這個描述符做驗證,要想通過驗證Binder通信的兩端DESCRIPTOR必須相同,這也是為什么我們在使用AIDL幫助我們生成Binder代碼的時候,必須把AIDL放在相同的包名下,因為SDK會根據包名為我們生成對應的DESCRIPTOR字符串,這里我們手寫Binder,只需要保證兩端相同就好了,包名字符串不是必須的

下面為Server端完整代碼

public class Stub extends android.os.Binder {
    //用于標識調用的Binder
    private static final java.lang.String DESCRIPTOR = "MyBinder";
    //方法標識,這里我們準備兩個方法,一個無參,一個有參
    private static final int TRANSACTION_method0 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    private static final int TRANSACTION_method1 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    @Override
    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
        switch (code) {
            case TRANSACTION_method0: {
                //Store or read an IBinder interface token
                //驗證Binder標識
                data.enforceInterface(DESCRIPTOR);
                //調用實現方法
                this.method0();
                reply.writeNoException();
                return true;
            }
            case TRANSACTION_method1: {
                data.enforceInterface(DESCRIPTOR);
                int _arg0;
                //按寫入的順序讀取數據
                _arg0 = data.readInt();           
                int _arg1;
                _arg1 = data.readInt();
                int _result = this.method1(_arg0, _arg1);
                reply.writeNoException();
                //向Client寫回返回值
                reply.writeInt(_result);          
                return true;
            }
        }
        return super.onTransact(code, data, reply, flags);
    }
    //Server端真正實現業務的兩個方法
    public void method0() throws RemoteException {
        Log.e("Server", Process.myPid() + "  " + Process.myTid() + "  " + Process.myUid() + "  " + "method0");
    }
    public int method1(int a, int b) throws RemoteException {
        Log.e("Server", Process.myPid() + "  " + Process.myTid() + "  " + Process.myUid() + "  " + "method1" + "  " + a + " " + b);
        return a + b;
    }
}

應用間要實現Binder通信必須要用Service來完成,想象客戶端要怎樣才能知道服務端的Binder地址并向其寫入數據,一種是客戶端通過一個Binder地址總管查詢,通過鍵名查找到對應的Binder服務,這種方式就是有名Binder,這個總管類就是ServiceManager,應用進程獲取系統服務就是通過查詢這個Binder總管實現的,比如應用進程啟動進入java層后就會去查找AMS的客戶端,就是通過ServiceManager來查找的,但作為應用進程,是不能向ServiceManager注冊有名Binder的,所以我們的客戶端也沒法通過ServiceManager查詢到對應的Binder服務端,但應用進程間依然是可以獲取到對方的Binder服務端的,Binder并不一定要注冊到ServiceManager才能被獲取到,這種Binder的獲取方式就是通過已經獲取到的Binder傳遞Binder,也就是說如果有某個有名Binder服務它提供了傳遞Binder的方法,那么我們就可以通過這個Binder服務來傳遞我們的匿名Binder,正好,AMS作為一個有名Binder提供了這個功能,其對Binder傳遞被封裝到了Service組件當中,我們可以通過Service.onBind 來返回我們要傳遞的匿名Binder客戶端,而在Activity.bindService中獲取到這個Binder:

    @Override
    public IBinder onBind(Intent intent) {
        return new Stub();
    }

在Client端Activity 中bindService,我們來看Activity的代碼:

    //定義常量
    //注意兩個工程中對應的標識符必須相同
    static final String DESCRIPTOR = "MyBinder";
    //方法標識
    static final int TRANSACTION_method0 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_method1 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent = new Intent();
        intent.setComponent(new ComponentName("com.xxx.server", "com.xxx.server.ServerService"));
        boolean b = bindService(intent, conn, BIND_AUTO_CREATE);
        Log.e("Client", "      "+b);
    }

bindService 的第二個參數,ServiceConnnection,在這個回調中我們取得IBinder對象,這個對象是Server端在Client中的一個代理對象

    ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder iBinder) {
            //這個IBinder對象iBinder,可以調用c++層Binder代理并最終通過Binder驅動傳遞數據
            Parcel _data0 = Parcel.obtain();//申請傳遞參數的Parcel對象(從Parcel池中取出)
            Parcel _reply0 = Parcel.obtain();//申請接收返回值的Parcel對象,相當于數據載體
            Parcel _data1 = Parcel.obtain();
            Parcel _reply1 = Parcel.obtain();
            try {
                //調用第一個方法
                //寫入Binder標識,以便服務端驗證
                _data0.writeInterfaceToken(DESCRIPTOR);
                //傳入方法標識,以便服務端知道我們要調用哪個方法,注意最后一個參數,就是上面提到的                  //flag,如果我們傳入IBinder.FLAG_ONEWAY,則這次調用為單邊調用,這個方法會立即返                //回,不會等服務端方法返回
                iBinder.transact(TRANSACTION_method0, _data0, _reply0, 0);
                _reply0.readException();
                //調用第二個方法
                _data1.writeInterfaceToken(DESCRIPTOR);
                //按順序寫入參數
                _data1.writeInt(1);
                _data1.writeInt(2);
                //從下面這行代碼開始本線程會阻塞,直到服務端進程中調用的方法完成計算返回后這個線程繼                 //續運行,計算的返回值放入_reply1中
                iBinder.transact(TRANSACTION_method1, _data1, _reply1, 0);
                _reply1.readException();
                int i = _reply1.readInt();//從reply中讀取返回值,這里我們就得到了服務端計算后的結果
            } catch (RemoteException e) {
                e.printStackTrace();
            } finally {
                //回收Parcel
                _data0.recycle();
                _reply0.recycle();
                _data1.recycle();
                _reply1.recycle();
            }
            Log.e("Client", Process.myPid() + "  " + Process.myTid() + "  " + Process.myUid() + "  " + "method0");
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

通過以上代碼,我們就可以實現跨進程間的方法調用

我們可以對onServiceConnected方法里的代碼做一定封裝,使用Proxy類封裝對IBinder的操作,使得調用的時候更方便

public class Proxy {
    static final String DESCRIPTOR = "MyBinder";
    static final int TRANSACTION_method0 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_method1 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    private android.os.IBinder mRemote;
    Proxy(android.os.IBinder remote) {
        mRemote = remote;
    }

    public void method0() throws android.os.RemoteException {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            mRemote.transact(TRANSACTION_method0, _data, _reply, 0);
            _reply.readException();
        } finally {
            _reply.recycle();
            _data.recycle();
        }
    }

    public int method1(int a, int b) throws android.os.RemoteException {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        int _result;
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            _data.writeInt(a);
            _data.writeInt(b);
            mRemote.transact(TRANSACTION_method1, _data, _reply, 0);
            _reply.readException();
            _result = _reply.readInt();
        } finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }
}

此時ServiceConnection對象可以更改為

    ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder iBinder) {
            Proxy proxy = new Proxy(iBinder);
            try {
                proxy.method0();
                int i = proxy.method1(1,2);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };

如何模糊跨進程調用與進程內調用?

分析問題

  • onServiceConnected(ComponentName name, IBinder iBinder) 這個方法傳遞的IBinder接口對象是什么,在同進程與不同進程時有什么不一樣?
  • 如果通過bindService 傳遞過來的IBinder對象是同進程的,那我們還需要使用IBinder.transact傳遞數據嗎?要知道的Binder的使用需要層層調用并最終在內核空間進行一次數據復制
  • 如果我們想跨進程的時候創建 Proxy 類包裹 IBinder 對象的操作,同進程的時候直接強轉 IBinder 對象為我們定義的對象或接口,不通過代理類直接使用其方法,應該怎么做?

對第一個問題,我們在 asInterface 里面打印一下傳進來的IBinder實例是什么類型,發現如果是遠程調用,傳給我們的 iBinder 是 BinderProxy 類型,他在native層會對應一個C++的BpBinder,BpBinder 最終會通過Binder驅動跟Server端通信。如果是本地調用,打印出的類型為Stub,說明本地調用時,onServiceConnected傳過來的就是我們在Service的onBinde方法返回的Stub對象本身。在這個基礎上,為了讓遠程調用(通過我們新建Proxy封裝BinderProxy對象)和本地調用(直接調用繼承自Binder的Stub對象)統一,我們讓Proxy和Stub實現相同的接口,再實現一個靜態方法,根據傳遞的IBinder對象返回一個對象,這個對象實現我們定義的接口,在進程內調用時,這個對象就是Stub類及其子類對象,當跨進程調用時,這個對象就是Proxy實例,由于要考慮兩種情況,我們就需要在這個靜態方法中作出判斷,判斷傳遞的IBinder對象是本地對象還是遠程對象,再根據判斷決定直接強轉為我們定義的接口返回,或生成Proxy對象強轉返回。此時我們需要將兩份文件合并。

怎樣區分IBinder對象的具體類型,我們可以通過IBinder的queryLocalInterface(DESCRIPTOR)方法,得到IInterface對象,判斷是否為null:

    //定義靜態方法根據傳遞的IBinder對象返回實現相同接口的不同對象
    public static IMyInterface asInterface(IBinder iBinder){
        if ((iBinder == null)) {
            return null;
        }
        IInterface iin = iBinder.queryLocalInterface(DESCRIPTOR);
        if (((iin != null) && (iin instanceof IMyInterface))) {
            return ((IMyInterface)iin );
        }
        return new Proxy(iBinder);
    }

若為Stub則返回值就是Stub實例本身,讓我們看一下Binder.queryLocalInterface的實現

    /**
     * Use information supplied to attachInterface() to return the
     * associated IInterface if it matches the requested
     * descriptor.
     */
    public IInterface queryLocalInterface(String descriptor) {
        if (mDescriptor.equals(descriptor)) {
            return mOwner;
        }
        return null;
    }

若為代理類BindProxy則為空,我們看下其實現:

    public IInterface queryLocalInterface(String descriptor) {
        return null;
    }

將傳進來的descriptor與mDescriptor比較,若相同,說明這是進程內調用,返回mOwner,這個mOwner和mDescriptor是需要Binder調用attachInterface賦值,所以我們在Stub構造方法里調用這個方法,又因為這個方法需要的參數為IInterface,所以我們讓Stub實現這個接口,最后由于我們已經將Proxy和Stub文件合并,在我們需要給別的進程綁定我們的iBinder時,需要把這個文件添加到對應的應用里,我們這個就需要把Stub兩個方法的實現抽離出來,具體的實現放在我們的業務代碼里,讓Server端Stub子類去實現

public interface IMyInterface {
    //定義兩個方法
    void method0() throws RemoteException;
    int method1(int a, int b) throws RemoteException;

    //用于標示調用的Binder
    static final java.lang.String DESCRIPTOR = "MyBinder";

    //用于標識調用的方法
    static final int TRANSACTION_method0 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_method1 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);

    //定義靜態方法根據傳遞的IBinder對象返回實現相同接口的不同對象
    public static IMyInterface asInterface(IBinder iBinder){
        if ((iBinder == null)) {
            return null;
        }
        IInterface iin = iBinder.queryLocalInterface(DESCRIPTOR);
        if (((iin != null) && (iin instanceof IMyInterface))) {
            return ((IMyInterface)iin );
        }
        return new Proxy(iBinder);
    }

    public abstract class Stub extends android.os.Binder implements IMyInterface,IInterface {
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        @Override
        public IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case TRANSACTION_method0: {
                    data.enforceInterface(DESCRIPTOR);//Store or read an IBinder interface token
                    this.method0();                   //
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_method1: {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    _arg0 = data.readInt();           //按寫入的順序讀取數據
                    int _arg1;
                    _arg1 = data.readInt();
                    int _result = this.method1(_arg0, _arg1);
                    reply.writeNoException();
                    reply.writeInt(_result);          //向Client寫回返回值
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }
    }
    public class Proxy implements IMyInterface {
        static final String DESCRIPTOR = "MyBinder";
        private android.os.IBinder mRemote;

        Proxy(android.os.IBinder remote) {
            mRemote = remote;
        }

        @Override
        public void method0() throws android.os.RemoteException {
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                mRemote.transact(TRANSACTION_method0, _data, _reply, 0);
                _reply.readException();
            } finally {
                _reply.recycle();
                _data.recycle();
            }
        }

        @Override
        public int method1(int a, int b) throws android.os.RemoteException {
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            int _result;
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                _data.writeInt(a);
                _data.writeInt(b);
                mRemote.transact(TRANSACTION_method1, _data, _reply, 0);
                _reply.readException();
                _result = _reply.readInt();
            } finally {
                _reply.recycle();
                _data.recycle();
            }
            return _result;
        }

    }

}

將如上文件放在需要通信的兩個工程里,就可以實現Binder通信,如果同進程調用,就會直接使用返回的對象而不通過Binder機制。

可以看出調用總是由客戶端發起,由服務端運算結束。

怎么實現進程間互調?

Binder被設計為CS模式,其本身是不支持服務端主動調客戶端的,但我們可以有一些曲線救國的方式,觀察Parcel這個類,它是支持傳遞IBinder和IInterface接口的,我們可以在Client調用Server時將Client端定義的Binder服務傳遞至Server,Server端拿到這個Binder地址,就可以在服務端也創建一個BinderProxy,就可以像Client端調用它一樣調用Client端的Binder,這時雙方角色互換,應用進程啟動時就是這樣把自己的ApplicationThread 通過AMS傳遞給system_server進程的,所以system_server在應用啟動并主動綁定AMS后就可以通過ApplicationThreadProxy來遠程調用應用的方法從而管理應用了。

觀察Parcel傳遞IInterface這個方法

public final void writeStrongInterface(IInterface val) {
  writeStrongBinder(val == null ? null : val.asBinder());
}

實際還是傳遞的 IBinder,這里回去看我們實現的Binder通信的代碼,我們只讓 Stub 實現了 IInterface ,然而我們希望在傳遞 IInterface 時不用去區分是 Stub 還是 Proxy,我們讓 Proxy 也實現IInterface這個接口,現在Stub和Proxy都實現我們定義的IMyInterface和系統提供的IInterface這兩個接口,所以我們讓IMyInterface直接繼承IInterface就好了,現在Proxy需要重寫IInterface的asBinder,返回mRemote變量就好了。至此,我們完成一份完整的 Binder 封裝代碼,這份代碼和我們編寫 IMyInterface.aidl 文件編譯后編譯器為我們生成的 java 文件是一樣的,下面是使用Android Studio編寫aidl文件后編譯生成的java文件:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: /home/end/AndroidStudioProjects/Demo/demo2/src/main/aidl/com/xjh/end/demo2/IMyAidlInterface.aidl
 */
package com.xjh.demo;
// Declare any non-default types here with import statements

public interface IMyAidlInterface extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.xjh.demo.IMyAidlInterface {
        private static final java.lang.String DESCRIPTOR = "com.xjh.demo.IMyAidlInterface";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            //可以用于區分此次調用是進程內還是進程間,因為進程內調用的,Stub 子類對象也就是
            //服務端實例 的構造函數被調用過程中將 DESCRIPTOR 保存為了自己的成員變量,所以調用
            //obj.queryLocalInterface(DESCRIPTOR)得到的結果不為空(實例實現是返回Binder子類也            //是Stub子類對象本身),如果是代理Binder端,之前的代碼可以看出BinderProxy類重新的方法            //直接返回就是null,這就可以區分當前調用是
            //進程內還是進程間了
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.xjh.demo.IMyAidlInterface interface,
         * generating a proxy if needed.
         */
        public static com.xjh.demo.IMyAidlInterface asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.xjh.demo.IMyAidlInterface))) {
                return ((com.xjh.demo.IMyAidlInterface) iin);
                //若是進程內調用,直接強轉返回就可以
            }
            //若為進程間調用,需要用一個代理類封裝,這個代理類封裝了往Binder發消息的代碼,使得調用
            //想進程內調用一樣方便
            return new com.xjh.demo.IMyAidlInterface.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }
        //這個函數是處理客戶端發過來的消息,將消息解析,調用對應的方法,而這些方法需要使用者在子類中
        //重寫實現具體邏輯
        @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_method0: {
                    data.enforceInterface(DESCRIPTOR);
                    //調用子類實現
                    this.method0();
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_method1: {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    _arg0 = data.readInt();
                    int _arg1;
                    _arg1 = data.readInt();
                    //調用子類實現
                    int _result = this.method1(_arg0, _arg1);
                    reply.writeNoException();
                    //向客戶端寫入返回值
                    //android.os.Parcel data 和 reply 都只是數據的載體,至于數據具體是怎么
                    //通過Binder發送的,先不關心
                    reply.writeInt(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }
        //aidl生成的文件把Stub作為IMyAidlInterface接口的內部類,又把Proxy作為Stub的內部類,但是        // 是不是內部并沒有關系,這兩個內部類都是靜態的,放在哪里都一樣,只是對外隱藏了這個代理類,使得
        //使用者只能使用Stub.asInterface 來返回這個對象而不能直接使用這個類創建實例,使得對Binder
        //進程內和進程間的訪問都被封裝成接口訪問,模糊兩者的區別,體現了更好的封裝。
        private static class Proxy implements com.xjh.demo.IMyAidlInterface {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                //就是BinderProxy類的實例
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }
            
            //Proxy類的作用就是封裝了遠程調用時想Binder寫入數據的操作,
            //這里仍然以android.os.Parcel類對象作為數據載體
            @Override
            public void method0() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    //寫入描述符,以通過服務端驗證
                    _data.writeInterfaceToken(DESCRIPTOR);
                    //將數據寫入Binder
                    //數據的寫入最終就是寫入Binder驅動,數據寫入內核空間,而
                    //由于Binder的機制,有部分內核空間和用戶空間的邏輯地址映射到了同一塊物理地址,
                    //所以服務端進程不需要再把數據復制到用戶空間,
                    //這也是Binder進行進程間通信效率高的原因之一,
                    //只經過了一次數據拷貝,而像Socket,則需要經過兩次拷貝,
                    //先從A進程將數據寫入內核空間,由于進程間在內核空間共享邏輯地址,
                    //所以B進程在內核空間也可以訪問到這個數據,但由于沒有像Binder一樣做內存映射,
                    //進程的內核空間和用戶空間的邏輯地址不共享,
                    //在B進程的用戶空間就訪問不到這個數據,
                    //所以還要從內核空間再將數據拷貝到B進程的用戶空間,完成一次跨進程數據傳遞
                    mRemote.transact(Stub.TRANSACTION_method0, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public int method1(int a, int b) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                int _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(a);
                    _data.writeInt(b);
                    mRemote.transact(Stub.TRANSACTION_method1, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        static final int TRANSACTION_method0 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_method1 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }
    //這就是Stub未實現需要子類自己實現的方法,子類實現這兩個方法實現服務端邏輯
    public void method0() throws android.os.RemoteException;

    public int method1(int a, int b) throws android.os.RemoteException;
}

以上代碼算是我們分析AIDL的原理分析,我們定義的AIDL文件最終會生成以上代碼,可以看出數據在客戶端寫入和在服務端讀出都是用了Parcel作為載體,這個類作用很強大,其還內部封裝了對Serializable接口數據的Binder傳遞方法,我們會在另一篇文章中解讀這個類。

我們定義AIDL接口以后,只需要在Server端繼承Stub并實現我們定義的接口方法,在客戶端將傳遞過來的IBinder對象用 asInterface 方法封裝,然后我們就可以像使用本地對象一樣調用遠程對象了。

最終實現的效果,進程內調用時,就是直接調用實現類的方法(method0,method1),跨進程調用時,客戶端通過Proxy類往代理Binder寫值,在服務端進程里,再取出這些值,根據這些數據再去調用對應的 實現類的方法(method0,method1),在服務端,方法的調用是在Binder線程里進行的,遠程調用的每次調用在服務端都是在Binder線程里進行的,這些Binder線程由Binder線程池管理,也就是說如果是遠程調用,method0,method1是運行在Binder線程里的,那么如果想讓我們的調用在Server端主線程執行,我們需要在Server端主線程創建Handler來把消息通過handler再轉給主線程來實現,這是AMS管理應用組件生命周期的方式,這也是Android另一個進程間通信方式Messager的原理,其實就是封裝了Binder和Handler,Binder跨進程發過來的消息立即轉到Handler,再用Handler把消息從Binder線程轉到指定線程,在指定線程中處理,這里我們即使不看代碼也應該可以判斷,Message的消息發送都是單邊調用,消息一旦發出不會等待調用結果返回。

要注意的是,如果我們是在App Process的UI thread 里面雙邊調用遠程對象,顯然和調用本地對象一樣,這個調用不能是耗時操作,UI thread 會等待遠端方法返回后再繼續運行。

這里貼一下以上java代碼對應的AIDL文件,SDK就是根據這個文件生成了以上一大串java代碼:

// IMyAidlInterface.aidl
package com.xjh.demo;

// Declare any non-default types here with import statements

interface IMyAidlInterface {
    void method0();
    int method1(int a, int b);
}

如何使用AIDL

AIDL接口支持哪些數據類型

  • Java 基本數據類型
  • String和CharSequence
  • Parcelable
  • List 和 Map(泛型類型必須是以上類型)
  • AIDL接口

aidl的使用需要我們聲明對應的aidl接口,編譯時SDK會根據aidl文件生成相應的java代碼,其功能就是封裝Binder的操作,使跨進程調用和本地調用一樣方便,上面已經貼出傳遞基本類型的AIDL代碼,而對于對象的傳遞我們需要用到Parcelable接口,而在aidl接口中的方法,其 Parcelable 類型的參數不管是不是屬于同一個包都需要 import,aidl 接口也是,除此之外,形參還需要指定 in | out | inout 類型,基本類型默認且只能是in類型,out類型指Binder服務端不讀取從客戶端傳過來的數據,而直接創建空對象,在這次調用的末尾再把這個對象再傳遞回客戶端,inout就是接收客戶端傳過來的數據,生成對象,調用過后再把這個對象傳遞回去。

所有需要在Binder中傳遞的 Parcelable 的實現類都需要創建與其名相同的 aidl 文件,這個文件中不必寫接口和方法,如我要實現一個Book 類,實現Parcelable接口:

package com.xxx.xxx;

import android.os.Parcel;
import android.os.Parcelable;

public class Book implements Parcelable{
    public String name;
    public int price;

    protected Book(Parcel in) {
        name = in.readString();
        price = in.readInt();
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(price);
    }
}

// Book.aidl
package com.xxx.xxx;
parcelable Book;

以上可知我們實現 Parcelable 這個接口,主要的工作就是把我們想要傳遞的對象包含的數據寫入 Parcel 以及從Parcel中取出數據生成對應的對象,其實還是把對象拆成基本類型,再在另一端再次生成對象,Binder底層傳遞的還是基本類型,其實對于安卓的另一個序列化接口Serializable,Parcel也就會將其反射取出數據裝入byte數組,然后在另一端取出來反射再生成對象,Binder底層傳遞的還是基本類型,我們將用一篇文章講解Parcel這個類。

復習下這張圖:


Binder.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 毫不夸張地說,Binder是Android系統中最重要的特性之一;正如其名“粘合劑”所喻,它是系統間各個組件的橋梁...
    weishu閱讀 17,951評論 29 246
  • 原文:http://weishu.me/2016/01/12/binder-index-for-newer/ 要點...
    指尖流逝的青春閱讀 2,618評論 0 13
  • Jianwei's blog 首頁 分類 關于 歸檔 標簽 巧用Android多進程,微信,微博等主流App都在用...
    justCode_閱讀 5,963評論 1 23
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,973評論 19 139
  • 臨近年節,朋友圈的一條短語引起了莫名的唏噓,原來我們一直都是騙子,從小到大,而且只對最親近的人…… 1.流鼻涕的年...
    海飛廉閱讀 474評論 6 21