該篇文章對AIDL
進行了初步的分析。通過理解aidl
自動生成的代碼,瞭解AIDL在不同進程間是如何通信的以及in
、out
、inout
修飾參數的具體作用。
1、AIDL的使用
先稍微看下AIDL
的使用。IDE
會根據aidl
文件自動生成代碼,而這些代碼就是服務端與客戶端之間溝通的橋樑。就比如說中國人和日本人語言無法交流,怎麼辦呢?那麼需要英語作爲中間語言,AIDL
就充當了英語的角色。
寫了個小Demo
,下面介紹下大概的使用步驟。
1.1、 定義aidl文件
這邊會介紹傳遞自定義數據也就是Parcel
數據加上一些基本數據,至於Parcel
序列化的知識Mark一下先用着後面再瞭解。下面是Demo
中的aidl
文件,定義了四個方法。
void basicTypes(in int anInt, long aLong, boolean aBoolean, float aFloat,double aDouble, String aString);
int getPid();
String bookIn(in com.example.aidldemo.entity.Book mbook);
String bookOut(out com.example.aidldemo.entity.Book mbook);
String bookInout(inout com.example.aidldemo.entity.Book mbook);
這裏面的in
、out
、inout
功能稍後會詳細介紹。寫好aidl
文件之後,IDE
編譯的時候會自動生成一個java
文件用於aidl
通信,在第二部分的源碼分析會重點介紹。
1.2、 服務端實現接口
在服務端是具體的實現類,實現了aidl
中定義的方法。貼上代碼:
@Override
public IBinder onBind(Intent intent) {
return myBind;
}
IMyAidlInterface.Stub myBind = new IMyAidlInterface.Stub(){
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
@Override
public int getPid() throws RemoteException {
return Process.myPid();
}
@Override
public String bookIn(Book mbook) throws RemoteException {
return null;
}
@Override
public String bookOut(Book mbook) throws RemoteException {
return null;
}
@Override
public String bookInout(Book mbook) throws RemoteException {
return null;
}
};
IMyAidlInterface
是根據aidl
文件自動生成的類。Stub
是其內部抽象類,Stub
它繼承了Binder
同時也要實現aidl
所定義的接口。
1.3、 客戶端綁定服務端
客戶端綁定服務並獲取服務端Stub
實現類的代理。主要代碼如下:
bindService(mIntent, connection, BIND_AUTO_CREATE);
ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
//myAidlInterface就是服務端的代理
myAidlInterface = IMyAidlInterface.Stub.asInterface(iBinder);
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
OK!到這邊使用方法介紹完畢。
2、源碼分析
2.1、整體介紹
現在看下系統根據aidl
文件自動生成的IMyAidlInterface
類結構圖。
可以看出IMyAidlInterface
其實也是個接口。其中包括了Stub
抽象內部類以及幾個需要實現的方法,Stub
中又包含了Proxy
代理類。
OK,那麼這邊先重點說下Stub
這個類再探討下Proxy
類。
2.2、Stub
構造以及asInterface
方法
public static abstract class Stub extends android.os.Binder implements IMyAidlInterface
Stub
繼承了Binder
並重寫了Binder
的一些重要方法(後面會介紹)而且implements了IMyAidlInterface
接口,也就是說他的對象也要實現IMyAidlInterface
接口。那麼就具有了Binder
以及IMyAidlInterface
的特性,換一句話說就是擁有進程間通信能力的同時也持有IMyAidlInterface
的實現方法。所以客戶端和服務端都是在Stub對象上進行操作的。
下面陸續介紹Stub方法裏面的功能:
private static final String DESCRIPTOR = "com.example.aidldemo.IMyAidlInterface";
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.aidldemo.IMyAidlInterface interface,
* generating a proxy if needed.
*/
public static IMyAidlInterface asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof IMyAidlInterface))) {
return ((IMyAidlInterface) iin);
}
return new Stub.Proxy(obj);
}
構造函數調用了attachInterface
方法,這個也是Binder源碼中的方法。跟進去看看,貼上代碼:
/**
* Convenience method for associating a specific interface with the Binder.
* After calling, queryLocalInterface() will be implemented for you
* to return the given owner IInterface when the corresponding
* descriptor is requested.
*/
public void attachInterface(IInterface owner, String descriptor) {
mOwner = owner;
mDescriptor = descriptor;
}
該方法就是將descriptor
與owner
兩個賦值給Stub
對象的本地變量,也可以說是將兩者綁定。
注意這個Stub()
構造方法是在服務端調用的,所以傳進去的owner也就是服務端持有的Stub
對象!
這個先放着接下來看asInterface
方法。其中比較重要的是queryLocalInterface
方法,這個也是Binder的方法。貼上代碼:
/**
* 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;
}
其實從註釋也可以看出他和attachInterface
方法是對應的。服務端的Stub對象構造時通過attachInterface
將自己和descriptor
傳進去,而這個queryLocalInterface
是連接成功後回調給客戶端的Ibider
對象裏面裏調用的。
那可以這樣做總結:對比客戶端得到的IBinder
對象的descriptor
與服務端的Stub
對象構造時傳進去的descriptor
是否一致。相等的話就返回服務端的Stub
對象,不相等的話返null
。
那什麼時候相等呢?同一個進程內的話相等,否則不相等。至於爲什麼同一個進程內就相等,這個就涉及到binService
的機制決定返回那個了,這邊先Mark一下後續有空分析。
說下個人對這種寫法的理解,到這邊有橋接模式的思維。客戶端只需調用IMyAidlInterface.Stub.asInterface(iBinder)
獲取IMyAidlInterface
對象以調用方法即可,但是是相同進程的處理方式還是不同進程下的處理方式對客戶端是透明的。
2.3、Proxy代理內部類
下面介紹下不同進程情況下後續流程,我們先看下Proxy
這個類。
private static class Proxy implements IMyAidlInterface
他實現了IMyAidlInterface
接口,也正是這個接口的實現類承載了進程通信的功能。這個是用到了代理模式,客戶端只是傳進來一個IBinder
。可以理解爲客戶端告訴代理我要和服務端的某服務溝通,然後就可以通過代理把自己的需求交給該服務處理。
接下來一步步看Proxy
代碼:
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
構造函數很簡單,就是將參數remote
賦值給局部變量mRemote
,而remote就是客戶端連接成功後回調回來的IBinder
對象。得到mRemote
之後就可以利用它來與服務端通信了。
接下來先看下這比較簡單的方法我在aidl
文件定義的getPid()
方法。在Proxy
類中實現了該方法,下面看下該方法:
static final int TRANSACTION_getPid = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
private static final String DESCRIPTOR = "com.example.aidldemo.IMyAidlInterface";
private static class Proxy implements IMyAidlInterface {
private android.os.IBinder mRemote;
....
@Override
public int getPid() 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);
mRemote.transact(Stub.TRANSACTION_getPid, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
首先該方法提供給客戶端調用,所以getPid()
運行於客戶端!
總結下該方法的三個步驟:
- 定義三個
Parcel
變量:data
、reply
、result
- 調用
mRemote.transact
方法 - 從
reply
取數據,並返回result
現在看下Stub中onTransact
方法,他是重寫Biner
的方法onTransact
方法。那什麼時候會回調該方法呢,先看下Biner
源碼中的註釋。
/**
* Default implementation is a stub that returns false. You will want
* to override this to do the appropriate unmarshalling of transactions.
*
* <p>If you want to call this, call transact().
*/
protected boolean onTransact(int code, Parcel data, Parcel reply,
int flags) throws RemoteException
可以看出調用transact()
方法就會回調該方法,也就是上面的mRemote.transact
方法會觸發服務端Stub
的onTransact
方法(服務端持有一份一樣的Stub
類)。
onTransact
()方法運行在Binder
線程池中!
至於爲什麼後續寫分析Binder
的時候理解下,這邊先MARK
現在看下Stub
的代碼的onTransact
方法,貼上代碼:
static final int TRANSACTION_getPid = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
private static final String DESCRIPTOR = "com.example.aidldemo.IMyAidlInterface";
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
String descriptor = DESCRIPTOR;
switch (code) {
case TRANSACTION_getPid: {
data.enforceInterface(descriptor);
int _result = this.getPid();
reply.writeNoException();
reply.writeInt(_result);
return true;
}
}
}
code
是int
型,用它來判斷當前要執行的是哪個方法。這邊要注意一下,做項目中會出現這樣的問題。當服務端修改aidl
文件之後,通過aidl
調用的方法會錯亂掉,本質原因就是服務端和客戶端的code
對應不上。
OK!接下來看下 int _result = this.getPid()
執行服務端Stub對象的getPid
方法並返回給result
,接下來往reply
裏寫result
數值。
onTransact
回調方法有兩個步驟:
- 執行服務端的方法
getPid()
- 獲取結果數據並寫入
reply
中
以上就是客戶端從調用方法到獲取結果的過程。
2.4、in,out,inout修飾作用
下面介紹下aidl的參數的幾個修飾用法in,out,inout。先看官方註釋:
When defining your service interface, be aware that:
- Methods can take zero or more parameters, and return a value or void.
- All non-primitive parameters require a directional tag indicating which way the data goes. Eitherin, out, or inout (see the example below).
- Primitives are in by default, and cannot be otherwise.
- Caution: You should limit the direction to what is truly needed, because marshalling parameters is expensive.
- All code comments included in the .
aidl
file are included in the generatedIBinder interface (except for comments before the import and package statements).- Only methods are supported; you cannot expose static fields in AIDL.
其中我們就看兩點。第一點:所有非基本類必須標明數據流向,所有基本類默認爲in且不能被其他標誌修飾。第二點是應該使用真正需要的數據流向,因爲開銷很大。第二點先Mark一下。關於第一點,基於基本類型默認爲in
,那麼就不考慮了。
接下來就分析參數爲自定義Parcel類型用in
、out
、inout
三種修飾的情況。
首先看下標明爲in的情況,貼上代碼:
private static class Proxy implements IMyAidlInterface {
private android.os.IBinder mRemote;
....
@Override
public String bookIn(com.example.aidldemo.entity.Book mbook) throws android.os.RemoteException{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((mbook != null)) {
_data.writeInt(1);
mbook.writeToParcel(_data, 0);//標註1
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_bookIn, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
String descriptor = DESCRIPTOR;
switch (code) {
case TRANSACTION_bookIn: {
data.enforceInterface(descriptor);
com.example.aidldemo.entity.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.example.aidldemo.entity.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
//調用bookIn(Book)方法
String _result = this.bookIn(_arg0);
reply.writeNoException();
reply.writeString(_result);
return true;
}
}
}
首先是標註1,先往data裏寫入mBook
數據然後調用mRemote.transact
方法。那麼進入onTransact
方法看一下,它是從data
獲取數據到arg0
,然後作爲參數傳入bookIn
方法。接下來將result
寫到reply
中,返回true表示正常。transact
執行結束後回到Proxy
的bookIn
實現類中,reply將讀取到的返回值回傳給result,整個過程結束。
OK!到這邊,mBook
數據作爲參數就從客戶端傳給了服務端,而客戶端並不會影響mBook
數據。這個就是in的功能,從客戶端到服務端的單向數據流。
看下標明爲out
的情況,貼上代碼:
private static class Proxy implements IMyAidlInterface {
private android.os.IBinder mRemote;
....
@Override
public String bookOut(com.example.aidldemo.entity.Book mbook) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_bookOut, _data, _reply, 0);//標註1
_reply.readException();
_result = _reply.readString();
if ((0 != _reply.readInt())) {
mbook.readFromParcel(_reply);
}
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
String descriptor = DESCRIPTOR;
switch (code) {
case TRANSACTION_bookOut: {
data.enforceInterface(descriptor);
com.example.aidldemo.entity.Book _arg0;
_arg0 = new com.example.aidldemo.entity.Book();
String _result = this.bookOut(_arg0);
reply.writeNoException();
reply.writeString(_result);
if ((_arg0 != null)) {
reply.writeInt(1);
_arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else {
reply.writeInt(0);
}
return true;
}
}
}
首先是標註1直接執行mRemote.transact
,並沒有用到傳進來的參數~。那麼直接看onTransact
方法可以看到他是新創建一個Book類賦值給arg0
。接下來調用bookOut
方法得到result寫入reply,還有一步重要就是將arg0
寫入reply
(這個步驟說明了數據是從服務端返回給客戶端)。接下來執行完transact返回到Proxy
的bookOut
方法,除了從_reply獲取數值到_result,還從reply獲取值到mbook
中。
OK!總結一下,也就是說從不用客戶端傳進來的參數,而是從服務端新建一個對象處理完之後返回給客戶端的參數對象。也就是實現了mbook
數據從服務端流向了客戶端,而客戶端的值並未流向服務端。
看下標明爲inout的情況,貼上代碼:
private static class Proxy implements IMyAidlInterface {
private android.os.IBinder mRemote;
....
@Override
public String bookInout(com.example.aidldemo.entity.Book mbook) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((mbook != null)) {
_data.writeInt(1);
mbook.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_bookInout, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
if ((0 != _reply.readInt())) {
mbook.readFromParcel(_reply);
}
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
String descriptor = DESCRIPTOR;
switch (code) {
case TRANSACTION_bookInout: {
data.enforceInterface(descriptor);
com.example.aidldemo.entity.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.example.aidldemo.entity.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
String _result = this.bookInout(_arg0);
reply.writeNoException();
reply.writeString(_result);
if ((_arg0 != null)) {
reply.writeInt(1);
_arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else {
reply.writeInt(0);
}
return true;
}
}
}
首先方法bookInout
的參數mbook
寫入data。然後調用mRemote.transact
方法。那麼看下onTransact
方法,data的數據傳入給了arg0
然後作爲參數調用bookInout
方法。到這邊,實現了數據從客戶端流向服務端。接下來將arg0
寫入reply並返回。
OK!回頭再看一下bookInout
方法。從reply
中讀取數據並賦值給mBook
,這就實現了數據從服務端流向客戶端。可以看出標明爲inout
的情況也就是數據雙向流動的。
1、對Parcel
的作用以及特點進行分析。
2、分析bindService
的源碼,進一步瞭解Binder
的機制。