1. 程式人生 > >Android基礎——初學者必知的AIDL在應用層上的Binder機制

Android基礎——初學者必知的AIDL在應用層上的Binder機制

初學者必知的AIDL在應用層上的Binder機制

首先得理解幾個概念:

IPC:Inter-Process Communication,程序間的通訊或跨程序通訊。簡單點理解,一個應用可以存在多個程序,但需要資料交換就必須用IPC;或者是二個應用之間的資料交換。

Binder:Binder是Android的一個類,它實現了IBinder介面。從IPC角度來說,Binder是Android中的一種跨程序通訊方式。通過這個Binder物件,客戶端就可以獲取服務端提供的服務或資料,這裡的服務包括普通服務和基於AIDL的服務。

AIDL:Android Interface Definition language,它是一種Android內部程序通訊介面的描述語言。

一、AIDL的使用


服務端:

建立一個服務端工程,在工程中點選右鍵New->AIDL->AIDL File,預設直接點確定,這時會在工程中出現一個aidl檔案:


我們開啟這個aidl檔案,我們建立一個我們需要測試的方法:


由於Android Studio是要手動編譯才能生成對應AIDL的java檔案,既然aidl檔案是個介面,那就必須存在著實現這個介面的類,點選編譯,系統自動生成一個java類,該java類的程式碼就是整個Binder機制的原理所在(會在下面第二步驟介紹原理):


既然是個服務端,那麼我們就要開始寫服務了,建立一個類,繼承Service:

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;

import com.handsome.boke.IMyAidlInterface;

public class MyService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return myS;
    }

    private IBinder myS = new IMyAidlInterface.Stub() {

        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {

        }

        @Override
        public int add(int num1, int num2) throws RemoteException {
            Log.i("Hensen", "從客戶端發來的AIDL請求:num1->" + num1 + "::num2->" + num2);
            return num1 + num2;
        }
    };
}

既然是個服務,就必須在manifests檔案中配置:

<!--exported:允許外界訪問該服務,AIDL必備條件-->
        <service
            android:name=".Aidl.MyService"
            android:exported="true"/>
到現在服務端寫好了,開啟模擬器啟動這個程式,記得在程式碼中開啟服務:
public class LoginActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        startService(new Intent(this, MyService.class));
    }
}

客戶端:

在工程中點選右鍵New->Module,按預設確定,finish:


關鍵的一步來了,
複製服務端的aidl整個資料夾(包括裡面的包、aidl檔案、完整無缺)貼上到客戶端對應放aidl的地方


不要忘了,客戶端還要
手動編譯


好了我們來寫客戶端的程式碼(我們在MainActivity中放一個”AIDL“的按鈕,先繫結服務,然後點選按鈕呼叫):

public class MainActivity extends AppCompatActivity {

    IMyAidlInterface iMyAidlInterface;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //繫結服務
        Intent intent = new Intent();
        intent.setComponent(new ComponentName("com.handsome.boke", "com.handsome.boke.Aidl.MyService"));
        bindService(intent, conn, BIND_AUTO_CREATE);
    }

    /**
     * 點選“AIDL”按鈕事件
     *
     * @param view
     */
    public void add(View view) {
        try {
            int res = iMyAidlInterface.add(1, 2);
            Log.i("Hensen", "從服務端呼叫成功的結果:" + res);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * 服務回撥方法
     */
    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            iMyAidlInterface = null;
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //解綁服務,回收資源
        unbindService(conn);
    }
}
測試結果(先開啟服務端,開啟服務後,接著開啟客戶端,繫結遠端服務):
08-19 10:59:34.548 6311-6328/com.handsome.boke I/Hensen: 從客戶端發來的AIDL請求:num1->1::num2->2
08-19 10:59:34.550 7052-7052/com.handsome.app2 I/Hensen: 從服務端呼叫成功的結果:3

二、AIDL的Binder機制原理分析


分析原理:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: D:\\workspace5\\Boke\\app\\src\\main\\aidl\\com\\handsome\\boke\\IMyAidlInterface.aidl
 */
package com.handsome.boke;
// 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.handsome.boke.IMyAidlInterface {
        private static final java.lang.String DESCRIPTOR = "com.handsome.boke.IMyAidlInterface";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.handsome.boke.IMyAidlInterface interface,
         * generating a proxy if needed.
         */
        public static com.handsome.boke.IMyAidlInterface asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.handsome.boke.IMyAidlInterface))) {
                return ((com.handsome.boke.IMyAidlInterface) iin);
            }
            return new com.handsome.boke.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_basicTypes: {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    _arg0 = data.readInt();
                    long _arg1;
                    _arg1 = data.readLong();
                    boolean _arg2;
                    _arg2 = (0 != data.readInt());
                    float _arg3;
                    _arg3 = data.readFloat();
                    double _arg4;
                    _arg4 = data.readDouble();
                    java.lang.String _arg5;
                    _arg5 = data.readString();
                    this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
                    reply.writeNoException();
                    return true;
                }
                case TRANSACTION_add: {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    _arg0 = data.readInt();
                    int _arg1;
                    _arg1 = data.readInt();
                    int _result = this.add(_arg0, _arg1);
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.handsome.boke.IMyAidlInterface {
            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;
            }

            /**
             * Demonstrates some basic types that you can use as parameters
             * and return values in AIDL.
             */
            @Override
            public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(anInt);
                    _data.writeLong(aLong);
                    _data.writeInt(((aBoolean) ? (1) : (0)));
                    _data.writeFloat(aFloat);
                    _data.writeDouble(aDouble);
                    _data.writeString(aString);
                    mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public int add(int num1, int num2) 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(num1);
                    _data.writeInt(num2);
                    mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }

    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;

    public int add(int num1, int num2) throws android.os.RemoteException;
}

我們來分析一下這個類

首先本身繼承Iinterface,所以他也是個介面,介面中必須有方法,程式碼定位到結尾有2個方法。

public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;
    public int add(int num1, int num2) throws android.os.RemoteException;
這兩個方法就是basicTypes和add,就是我們服務端的2個方法。

接著發現該介面中有1個內部類Stub,繼承自本身(IMyAidlInterface)介面,程式碼定位到Stub類。

這個Stub有個構造方法、asInterface、asBinder、onTransact(先不介紹)。

接著發現該內部類Stub還有一個內部類,程式碼定位到Proxy(我們把它稱為代理)類,也是繼承自本身(IMyAidlInterface)介面,所以實現該介面的兩個方法。

/**
             * Demonstrates some basic types that you can use as parameters
             * and return values in AIDL.
             */
            @Override
            public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(anInt);
                    _data.writeLong(aLong);
                    _data.writeInt(((aBoolean) ? (1) : (0)));
                    _data.writeFloat(aFloat);
                    _data.writeDouble(aDouble);
                    _data.writeString(aString);
                    mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public int add(int num1, int num2) 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(num1);
                    _data.writeInt(num2);
                    mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }
在這個類裡面我們會發現有2個標識:用來區分兩個方法,到底你遠端請求哪個方法的唯一標識,程式碼定位到代理類的結尾
 static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
回過頭來,還記得我們客戶端做了什麼嗎?答案:繫結一個服務,在回撥方法獲取一個介面(iMyAidlInterface),它是直接靜態使用IMyAidlInterface裡面的靜態類Stub的asInterface的方法:(好了我們去跟蹤到Stub類asInterface這個方法)
 /**
     * 服務回撥方法
     */
    private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            iMyAidlInterface = null;
        }
    };
程式碼定位到Stub類asInterface方法
public static com.handsome.boke.IMyAidlInterface asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.handsome.boke.IMyAidlInterface))) {
                return ((com.handsome.boke.IMyAidlInterface) iin);
            }
            return new com.handsome.boke.IMyAidlInterface.Stub.Proxy(obj);
        }
前面只是做一些判斷、看一下最後一句話:我們將傳過來的obj還是傳給了它的代理類來處理,返回的是代理類的物件
return new com.handsome.boke.IMyAidlInterface.Stub.Proxy(obj);
所以在客戶端的iMyAidlInterface = ……,則是拿到它的代理類,好了,這個時候就看客戶端呼叫代理類幹嘛了
int res = iMyAidlInterface.add(1, 2);
他呼叫了代理類的add方法,程式碼定位到代理類的add方法
@Override
            public int add(int num1, int num2) 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(num1);
                    _data.writeInt(num2);
                    mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
你會發現,它把資料寫進了_data裡面,最後呼叫transact方法,傳入_data資料,唯一標識Stub.TRANSACTION_add。
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
然後這個transact方法就是通過底層了,通過底層結束後,這些引數送到哪了?答案:底層會走到stub類中的onTransact方法,通過判斷唯一標識,確定方法:
case TRANSACTION_add: {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    _arg0 = data.readInt();
                    int _arg1;
                    _arg1 = data.readInt();
                    int _result = this.add(_arg0, _arg1);
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
在這個地方將傳過來的引數解包,readInt方法。然後呼叫this.add方法,this指的就是服務端,呼叫服務端的add的方法:
int _result = this.add(_arg0, _arg1);
將得到的結果,寫入reply
reply.writeNoException();
                    reply.writeInt(_result);
最後一句話,最後返回系統的ontransact方法,傳入結果reply:
return super.onTransact(code, data, reply, flags);
所以我們在上面獲得的結果就是reply(答案:3):
int res = iMyAidlInterface.add(1, 2);

最後總結下整個過程



三、AIDL編寫時候的一些錯誤

錯誤一:

Caused by: java.lang.NullPointerException: Attempt to invoke interface method 'int com.handsome.boke.IMyAidlInterface.add(int, int)' on a null object reference
這個錯誤很有可能是你寫的服務端,忘記返回myS了,返回的是個null
@Override
    public IBinder onBind(Intent intent) {
        return null;
    }

錯誤二:

 Caused by: java.lang.SecurityException: Not allowed to bind to service Intent { cmp=com.handsome.boke/.Aidl.MyService }
這個錯誤很有可能是的服務端在manifests檔案中少了exported="true"的屬性
<!--exported:允許外界訪問該服務,AIDL必備條件-->
        <service
            android:name=".Aidl.MyService"/>