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
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容

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