Android下AIDL機制詳解

李牙刷兒發表於2016-11-09

AIDL全名Android Interface Definition Language,是一種介面定義語言,也是Android系統的一種跨程式通訊機制。從AIDL的名字就可以看出來,AIDL做的就是在服務提供程式和服務使用程式之間的協商好的介面,雙方通過該介面進行通訊。本文將以一個例子來講述AIDL的使用方式和流程,在下一篇文章中我將從程式碼層面對AIDL進行分析。

AIDL例項

文章中所涉及的例子來源於android開發之AIDL用法_程式間通訊原理詳解 一文。首先我們建立一個AIDL檔案,並建立了一個介面方法:

interface forService {  
    void registerTestCall(forActivity cb);  
    void invokCallBack();  
} 

這裡多說一句,在AIDL檔案中並不是所有的資料都可以使用,能夠使用的資料型別包括如下幾種:

    * 基本資料型別(int, long, char, boolean, double等)
    * String和CharSequence
    * List:只支援ArrayList,並且裡面的元素都能被AIDL支援
    * Map:只支援HashMap,裡面的每個元素能被AIDL支援
    * Parcelable:所有實現Parcelable介面的物件
    * AIDL: 所有AIDL介面本身也能在AIDL檔案中使用

另外AIDL中除了基本資料型別意外,其他資料型別必須標上方向:in、out或者inout。其實AIDL檔案和interface很像,其作用本質也是定義了一個介面,就像雙方制定的一個協議一樣,在通訊時必須遵守該協定才能正常通訊。

遠端服務端Service實現

package com.styleflying.AIDL;  
import android.app.Service;  
import android.content.Intent;  
import android.os.IBinder;  
import android.os.RemoteCallbackList;  
import android.os.RemoteException;  
import android.util.Log;  
public class mAIDLService extends Service { 
    ....  
    @Override  
    public void onCreate() {  
        Log("service create");  
    }   
      
    @Override  
    public IBinder onBind(Intent t) {  
        Log("service on bind");  
        return mBinder;  
    }   
    @Override  
    public boolean onUnbind(Intent intent) {  
        Log("service on unbind");  
        return super.onUnbind(intent);  
    }  
    public void onRebind(Intent intent) {  
        Log("service on rebind");  
        super.onRebind(intent);  
    }  
    private final forService.Stub mBinder = new forService.Stub() {  
        @Override  
        public void invokCallBack() throws RemoteException  
        {  
            callback.performAction();  
              
        }  
        @Override  
        public void registerTestCall(forActivity cb) throws RemoteException  
        {  
            callback = cb;  
              
        }  
          
    };  
} 

這裡注意一下onBind()函式,該函式返回了一個IBinder型別,IBinder說明AIDL底層是基於Android Binder機制實現的,Binder機制的具體實現細節放到下一篇部落格再做詳細介紹。onBind函式實際返回的是mBinder型別,而該型別實際是宣告瞭一個forService.stub型別,並在宣告中對AIDL檔案定義的介面方法做了具體的實現。所以這裡的mBinder可以理解為一個包含了AIDL所定義介面具體實現的類,而這個類最終將傳遞給客戶端,供其呼叫。

客戶端程式碼

    package com.styleflying.AIDL;  
    .... 
    public class mAIDLActivity extends Activity {  
        private static final String TAG = "AIDLActivity";    
        forService mService;  
        private ServiceConnection mConnection = new ServiceConnection() {  
            public void onServiceConnected(ComponentName className,  
                    IBinder service) {  
                mService = forService.Stub.asInterface(service);  
                try {  
                    mService.registerTestCall(mCallback);}  
                catch (RemoteException e) {  
                      
                }  
                }  
            public void onServiceDisconnected(ComponentName className) {  
                Log("disconnect service");  
                mService = null;  
                }  
            };     
    }  

ServiceConnection 是客戶端發起的一個指向服務端的連線,而在連線成功時(onServiceConnected被呼叫時),通過
mService =forService.Stub.asInterface(service)獲取到了服務端傳遞過來的包含有AIDL規定介面具體實現的AIDL物件(即service端onBind返回的物件,該物件原本是一個forService.stub物件通過呼叫asInterface介面獲取到了對應的AIDL物件),接下來就可以利用mService呼叫對應的方法了。然後在連線斷開時再釋放即可。

AIDL原始碼解析

上文即是一個AIDL的使用例項,利用AIDL可以輕鬆的實現在Android端的跨應用通訊。但知其然還要知其所以然,這樣簡單的使用顯然無法透徹的瞭解AIDL通訊的原理。上文我們已經提到了AIDL實際底層利用的是Android Binder機制進行通訊,在本文中我們將從程式碼層面繼續剖析AIDL機制,而在下一篇部落格中講解Binder機制的原理。雖然我們定義的AIDL檔案只有寥寥數行,但是真正的執行起來的AIDL程式碼卻遠遠不止這點,實際上大部分的工作IDE都幫我們完成了,以上文中的forService為例,在我們編寫完AIDL檔案後,IDE會生成其對應的java檔案forService.java

package ...;  
import java.lang.String;  
import android.os.RemoteException;  
import android.os.IBinder;  
import android.os.IInterface;  
import android.os.Binder;  
import android.os.Parcel;  
public interface forService extends android.os.IInterface  
{  
/** Local-side IPC implementation stub class. */  
public static abstract class Stub extends android.os.Binder implements com.styleflying.AIDL.forService  
{  
private static final java.lang.String DESCRIPTOR = "com.styleflying.AIDL.forService";  
/** Construct the stub at attach it to the interface. */  
public Stub()  
{  
this.attachInterface(this, DESCRIPTOR);  
}  
/** 
 * Cast an IBinder object into an forService interface, 
 * generating a proxy if needed. 
 */  
public static com.styleflying.AIDL.forService asInterface(android.os.IBinder obj)  
{  
if ((obj==null)) {  
return null;  
}  
android.os.IInterface iin = (android.os.IInterface)obj.queryLocalInterface(DESCRIPTOR);  
if (((iin!=null)&&(iin instanceof com.styleflying.AIDL.forService))) {  
return ((com.styleflying.AIDL.forService)iin);  
}  
return new com.styleflying.AIDL.forService.Stub.Proxy(obj);  
}  
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_registerTestCall:  
{  
data.enforceInterface(DESCRIPTOR);  
com.styleflying.AIDL.forActivity _arg0;  
_arg0 = com.styleflying.AIDL.forActivity.Stub.asInterface(data.readStrongBinder());  
this.registerTestCall(_arg0);  
reply.writeNoException();  
return true;  
}  
case TRANSACTION_invokCallBack:  
{  
data.enforceInterface(DESCRIPTOR);  
this.invokCallBack();  
reply.writeNoException();  
return true;  
}  
}  
return super.onTransact(code, data, reply, flags);  
}  
private static class Proxy implements com.styleflying.AIDL.forService  
{  
private android.os.IBinder mRemote;  
Proxy(android.os.IBinder remote)  
{  
mRemote = remote;  
}  
public android.os.IBinder asBinder()  
{  
return mRemote;  
}  
public java.lang.String getInterfaceDescriptor()  
{  
return DESCRIPTOR;  
}  
public void registerTestCall(com.styleflying.AIDL.forActivity cb) 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.writeStrongBinder((((cb!=null))?(cb.asBinder()):(null)));  
mRemote.transact(Stub.TRANSACTION_registerTestCall, _data, _reply, 0);  
_reply.readException();  
}  
finally {  
_reply.recycle();  
_data.recycle();  
}  
}  
public void invokCallBack() 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(Stub.TRANSACTION_invokCallBack, _data, _reply, 0);  
_reply.readException();  
}  
finally {  
_reply.recycle();  
_data.recycle();  
}  
}  
}  
static final int TRANSACTION_registerTestCall = (IBinder.FIRST_CALL_TRANSACTION + 0);  
static final int TRANSACTION_invokCallBack = (IBinder.FIRST_CALL_TRANSACTION + 1);  
}  
public void registerTestCall(com.styleflying.AIDL.forActivity cb) throws android.os.RemoteException;  
public void invokCallBack() throws android.os.RemoteException;  
}  

程式碼比較複雜,而且一看就是系統自動生成的,看起來很不規範。不用著急,我們一點一點來啃。首先這個類在內部實現了一個抽象類Stub,這個詞看起來是不是很熟悉,對了,它就是在Service端出現的那個stub。stub直譯過來是存根,也就是Service保留在本地的一個憑證。forService類中所有的邏輯都在stub中實現。而在stub中又有一個子類稱為proxy(代理),這個類名更好理解,他是service的代理,需要用到代理的地方只有遠端的呼叫者,即客戶端。所以proxy就是從服務端傳遞到客戶端的物件,客戶端正是通過這個代理來執行AIDL中的介面方法的。

Stub

接下來我們深入兩個類來看其程式碼,首先stub通過android.os.IInterfaceattachInterface方法來完成自我構造。接下來是asInterface方法,客戶端正是利用這個方法來獲取到遠端服務物件的。在該方法中,首先是在本地查詢是否有DESCRIPTOR所描述的類,如果存在直接返回;如果不存在就返回proxy。這說明當客戶端在呼叫該方法獲取遠端服務時,實際上服務端首先是會檢查服務端是否和客戶端在同一個程式中,如果在則直接返回自身,如果不是則返回proxy代理。
onTransact是指令的執行的函式,也即執行AIDL介面定義的函式實際上最終都會執行到這裡。data是傳入資料,reply是返回資料,兩個資料都是Parcel類說明在AIDL的通訊過程中資料必須經過序列化操作。不同的指令執行不同的switch分支:1)讀取資料;2)執行指令;3)返回資料。

Proxy

讀懂了Stub的原始碼,Proxy的原始碼就更加簡單了,Proxy類中有一個mRemote屬性,該屬性就是遠端的服務端stub。當客戶端利用proxy代理執行對應的方法時,proxy的執行邏輯都是:1)宣告傳入資料和返回結果兩個序列化物件;2)寫入傳入資料;3)執行方法,執行的邏輯就是呼叫mRemote的onTransact方法在遠端執行方法;4)執行完成後讀取返回結果

以上就是從程式碼層面來解析AIDL機制的實現原理,其實如果讀懂了程式碼,AIDL的實現原理也不難,stub-proxy模式也是一個常用的設計模式。但是資料和指令究竟是怎麼在兩個程式之間進行傳遞的,原始碼中卻沒有體現,這就需要了解AIDL的底層實現機制Binder了。


相關文章