AIDL使用規範及調用過程解析(Android Q)

AIDL使用介紹


AIDL的全稱是Android Interface definition language,一看就明白,它是一種android內部進程通信接口的描述語言,通過它我們可以定義進程間的通信接口,用處當然就是用來進程間的通信和方法調用了。其實AIDL是Binder的一個上層實現,它簡化了Binder的使用,在編譯時,由編譯器幫助我們完成了大量工作(例如,生成AIDL文件所對應的.java類)。

先介紹一下AIDL進程間通信的流程。

AIDL通信流程簡介

1. AIDL接口的創建

AIDL文件中,並不是所有的數據類型都是可以使用的,它支持的數據類型有:

  • 基本數據類型(int,long,char,boolean,double等)
  • String和CharSequence
  • List:只支持ArrayList,而且list中的元素也必須是 AIDL 支持的類型
  • Map:只支持HashMap,裏面的key和value也必須是AIDL支持的類型
  • Parceable:所有實現了 Parceable 接口的對象
  • AIDL:所有的 AIDL 接口本身也可以在 AIDL 文件中使用,所以 IBinder 類型也是支持的

2. 服務端

服務端首先要創建一個 Service 用來監聽客戶端的請求,然後將在對應AIDL文件中聲明的接口實現,並且通過onbind函數返回相應 IBinder 對象即可。

3. 客戶端

客戶端所要做的事情就稍微簡單一些,首先需要綁定服務端的Service,綁定成功後,將服務端返回的 IBinder 對象轉成AIDL接口所屬的類型,接着就可以調用AIDL中的方法了。

AIDL文件及參數說明

自定義類型

通常,AIDL默認支持基礎類型參數及返回值的數據傳遞。如果AIDL文件中用到了自定義的Parcelable對象,那麼必須新建一個和它同名的AIDL文件,並在其中聲明它爲Parcelable類型。

例如,我們定義了一個名爲RequestData的數據類型,要想在AIDL中使用它,除了它自己的RequestData.java文件之外,我們還必須定義一個名爲RequestData.aidl的文件(包名必須和RequestData.java中的相同),內容如下:

package com.testaidl;
parcelable RequestData;

參數標誌

AIDL中除了基本數據類型,其他類型的參數必須標上方向:in,out或者inout, in表示輸入型參數,out表示輸出型參數,inout表示輸入輸出型參數。

我們要根據實際需要去指定參數類型,不能一概使用out或者inout,因爲這在底層實現是有開銷的。

接口及同步

AIDL接口中只支持方法,不支持聲明靜態常量,這一點區別於傳統的接口。

客戶端調用服務端方法,被調用的方法運行在服務端的Binder線程池中,同時客戶端線程會被掛起,這個時候如果服務端方法執行比較耗時,就會導致客戶端線程長時間阻塞,而如果這個客戶端線程是UI線程的話,就會導致客戶端ANR,所以如果知道服務端的一個方法是耗時的,就要避免在客戶端的UI線程中去調用該遠程方法。

但是有一種方法可以發起非阻塞式的遠程調用:就是在聲明AIDL時,加上oneway關鍵字。

相比平常自定義的 aidl,多了 oneway 的關鍵字,聲明和不聲明 oneway 關鍵字的在於生成 Java 類中一個參數:
在這裏插入圖片描述在這裏插入圖片描述
不聲明 oneway 時,mRemote.transact 傳入的最後一個參數是 0;聲明 oneway 時,mRemote.transact 傳入的最後一個參數是 android.os.IBinder.FLAG_ONEWAY 。

FLAG_ONEWAY 的作用就是讓客戶端能夠非阻塞的調用遠程方法。

系統Service的調用示例


我們以系統服務的調用來分析AIDL的使用過程。

系統服務的調用過程

首先通過 ServiceManager.getService()方法獲取一個IBinder對象,但是這個IBinder對象不能直接調用,必須要通過asInterface方法轉成對應的IInterface對象纔可以使用,如果在同一個進程中(當然,這是跨進程通信,這種情況很少),就會直接返回通過attachInterface方法設置的IInterface對象(上面代碼所述),但是如果不是在同一個進程中,就會先通過IBinder對象創建一個Proxy對象(服務的本地代理對象),然後在Proxy對象中通過調用IBinder對象的transact方法調用到服務進程的Service中,實現了跨進程的通信。

以AMS爲例,我們來看具體實現。

AMS的服務調用關鍵實現

AIDL 文件生成的類中會自動生成兩個類,Proxy 類和 Stub 類,對應的是 ActivityManagerProxy 和 ActivityManagerService 類。

我們使用ActivityManager實現AMS通信時,在其內部都是調用ActivityManagerNative來實現的。我們來看相關類的繼承關係:

我們在應用層調用,都會調用到ActivityManagerNative.getDefault()方法,我們來看源碼:

    static public IActivityManager asInterface(IBinder obj) {
        if (obj == null) {
            return null;
        }
        IActivityManager in =
            (IActivityManager)obj.queryLocalInterface(descriptor);
        if (in != null) {
            return in;
        }

        return new ActivityManagerProxy(obj);
    }
    static public IActivityManager getDefault() {
        return gDefault.get();
    }
    
        private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
        protected IActivityManager create() {
            IBinder b = ServiceManager.getService("activity");
            if (false) {
                Log.v("ActivityManager", "default service binder = " + b);
            }
            IActivityManager am = asInterface(b);
            if (false) {
                Log.v("ActivityManager", "default service = " + am);
            }
            return am;
        }
    };
邏輯解析:
  1. getDefault返回一個Singleton對象。
  2. ServiceManager.getService(“activity”)返回AMS的IBinder對象,該對象需要轉換後使用。
  3. asInterface()方法把AMS的IBinder對象轉換爲可供使用的對象。

asInterface()方法調用結果會有2種情況,第一種是,如果調用者和被調用服務在同一個進程,則進行本地通信,直接返回當前對象即可;第二種是,如果調用者和被調用服務是在不同進程,則會通過服務的BinderProxy實現跨進程調用。我們分別來分析。

同一個進程

queryLocalInterface 函數如果不爲null,則在同一進程中通信。

queryLocalInterface 函數:

public IInterface queryLocalInterface(String descriptor) {
    if (mDescriptor.equals(descriptor)) {
        return mOwner;
    }
    return null;
}

我們來看mOwner是誰?

public void attachInterface(IInterface owner, String descriptor) {
    mOwner = owner;
    mDescriptor = descriptor;
}
public ActivityManagerNative() {
    attachInterface(this, descriptor);
}

mOwner是在ActivityManagerNative創建的時候賦值的,就是一個ActivityManagerNative對象。AMS的初始化是在SystemServer進程中,所以ActivityManagerNative構造函數是在SystemServer進程中調用的。

不同進程

如果queryLocalInterface 方法返回的是 null,就會創建一個ActivityManagerProxy對象(參數是IBinder對象),該對象是服務端AMS的代理對象,可跨進程通信。

return new ActivityManagerProxy(obj);

我們來看ActivityManagerProxy的部分實現:

class ActivityManagerProxy implements IActivityManager
{
    public ActivityManagerProxy(IBinder remote)
    {
        mRemote = remote;
    }

    public IBinder asBinder()
    {
        return mRemote;
    }

    public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
            String resolvedType, IBinder resultTo, String resultWho, int requestCode,
            int startFlags, ProfilerInfo profilerInfo, Bundle options) throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken(IActivityManager.descriptor);
        ...
        mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);
        reply.readException();
        ...
    }
}

可以看到,ActivityManagerProxy 的作用只是作爲代理而已,通過它,最終會調遠程(Service端)mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0)來實現具體的功能。

到了這裏,我們明白,如果是遠程調用,即Client和Server不在同一個進程中,queryLocalInterface必然返回null,這樣就會new一個ActivityManagerProxy對象來進行遠程調用。

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