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"/>