AIDL淺析

  • 前言

該篇文章對AIDL進行了初步的分析。通過理解aidl自動生成的代碼,瞭解AIDL在不同進程間是如何通信的以及inoutinout修飾參數的具體作用。

  • 源碼分析

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);

這裏面的inoutinout功能稍後會詳細介紹。寫好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;
}

該方法就是將descriptorowner兩個賦值給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()運行於客戶端!

總結下該方法的三個步驟:

  1. 定義三個Parcel變量:datareplyresult
  2. 調用mRemote.transact方法
  3. 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方法會觸發服務端StubonTransact方法(服務端持有一份一樣的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;
                }
            }
}

codeint型,用它來判斷當前要執行的是哪個方法。這邊要注意一下,做項目中會出現這樣的問題。當服務端修改aidl文件之後,通過aidl調用的方法會錯亂掉,本質原因就是服務端和客戶端的code對應不上。

OK!接下來看下 int _result = this.getPid() 執行服務端Stub對象的getPid方法並返回給result ,接下來往reply裏寫result數值。

onTransact回調方法有兩個步驟:

  1. 執行服務端的方法getPid()
  2. 獲取結果數據並寫入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類型用inoutinout三種修飾的情況。

首先看下標明爲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執行結束後回到ProxybookIn實現類中,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返回到ProxybookOut方法,除了從_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的機制。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章