從 Remote Service Binding 學習 AIDL 與 IPC

 

默認情況下,一個應用不管有多少個 Activity、Service 或其他組件,它們都是運行在一個進程上,但是我們可以安排 Service 運行一個新的進程上,但是不同進程之間應該如何通信呢?當需要在不同的進程之間傳遞對象時,應該怎麼做呢?AIDL(Android Interface Definition Language) 便是解決這一問題的鑰匙。

使用 AIDL 並不是難事,但是比較繁瑣,並且一不小心容易出錯。好在 Android Dev Guide 的 Designing a Remote Interface Using AIDL 對這個問題講解非常詳細,再結合 Android APIDemo 中的 Remote Service Binding 給出了的示例,這都給了開發者提供了非常到位的幫助。以下內容就是我結合二者,整理出來的筆記,力求真理,但能力有限,差錯難免,請讀者堅持自己的判斷力,本文只供參考,且不可盡信。

一、使用 AIDL 實現 IPC

面對問題,應統攬全局,歸納階段,劃定步驟,共得出四大步,分列如下:

創建.aidl 文件
在這個文件裏定義 method 和 field
把 .aidl 文件加入到 makefile 中去
如果使用 Eclipse 則 ADT 會幫你管理
實現接口方法
AIDL 編譯器會根據接口生成一個用 Java 語言編寫的interface,這個 interface 有一個抽象的內部類,名字爲 Stub,你必須創建一個類,繼承於它,並且實現 .adil 文件中所聲明的方法
公開接口給客戶端
如果創建的是 service,則應該繼承自 Service,並且重載 Service.onBind() 返回實現接口的類的實例

這四個步驟在 Remote Service Binding 中均有所呈現,以下分開闡述。Remote Service Binding 共包含有兩個 .java 文件,三個 .aidl 文件,物理結構比較簡單,但是邏輯結構就不那麼簡單,以下用 Class Diagram 來展示其中的關係。

Remote Service Binding

Remote Service Binding

1、創建.aidl 文件

AIDL 語法簡單,用來聲明接口,其中的方法接收參數和返回值,但是參數和返回值的類型是有約束的,且有些類型是需要 import,另外一些則無需這樣做。

AIDL 支持的數據類型劃分爲四類,第一類是 Java 編程語言中的基本類型,第二類包括 String、List、Map 和 CharSequence,第三類是其他 AIDL 生成的 interface,第四類是實現了 Parcelable protocol 的自定義類。

其中,除了第一類外,其他三類在使用時均需要特別小心。

使用第二類時,首先需要明白這些類不需要 import,是內嵌的。其次注意在使用 List 和 Map 此二者容器類時,需注意其元素必須得是 AIDL 支持的數據類型,List 可支持泛型,但是 Map 不支持,同時另外一端負責接收的具體的類裏則必須是 ArrayListHashMap

使用第三、四類時,需要留意它們都是需要 import 的,但是前者傳遞時,傳遞的是 reference,而後者則是 value。

在創建 .aidl 文件的過程中,應該注意一旦 method 有參數,則需注意在前面加上 in, out 或 inout,它們被稱爲 directional tag,但是對於基本類型的參數,默認就是 in,並且不會是其他值。

Remote Service Binding 共包括了三個 .aidl 文件,分別是IRemoteService.aidl、IRemoteServiceCallback.aidl、ISecondary.aidl,通過它們可以看到如何使用第一類和第三類的數據類型,稀罕的是,看不到第二類、第四類數據類型的使用,也沒有看到 directional tag。

2、實現 Interface

AIDL 爲你生成一個 interface 文件,文件名稱和 .aidl 文件相同。如果使用 Eclipse 插件,則 AIDL 會在構建過程中自動運行,如果不使用插件,則需要先使用 AIDL。

生成的 interface 會包含一個抽象內部類 Stub,它聲明瞭在 .aidl 文件裏的所有方法。Stub 也定義了一些幫助方法,比較常用的有 asInterface(),其接收一個 IBinder 作爲參數,並且返回一個 interface 的實例用來調用IPC方法。

    private static INotificationManager sService;
 
    static private INotificationManager getService()
    {
        if (sService != null) {
            return sService;
        }
        sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));
        return sService;
    }

要實現 interface,需要繼承 Stub,實現其方法,這在 RemoteServiceRemoteServiceBinding 都可以找到相關代碼。

這個環節是重中之重,需要特別小心的有兩點,其一是拋出的所有異常均不會發給調用者;其二是IPC調用是同步的,這意味IPC服務一旦花費較長時間完成的話,就會引起ANR,應該將這樣的操作放在單獨的線程裏。

3、向客戶端公開 Interface

獨樂樂不如衆樂樂,需要將服務公開出去,要達成這個目的,須得創建一個 Service 的子類,並且實現 Service.onBind(Intent),通過這個方法將實現了接口的類的實例返回回來。通過查看 RemoteService 一目瞭然。

    @Override
    public IBinder onBind(Intent intent) {
        // Select the interface to return.  If your service only implements
        // a single interface, you can just return it here without checking
        // the Intent.
        if (IRemoteService.class.getName().equals(intent.getAction())) {
            return mBinder;
        }
        if (ISecondary.class.getName().equals(intent.getAction())) {
            return mSecondaryBinder;
        }
        return null;
    }

其中的 mBinder 和 mSecondaryBinder 分別是實現了 IRemoteServiceISecondary 接口的類的實例。

4、使用Parcelables傳值

前文中提到 Remote Servcie Binding 沒有使用第四類數據類型作爲參數,這是示例的不足,要想讓一個類變成第四類,需要遵照以下步驟:

  1. 引入 Parcelable 接口
  2. 實現 writeToParcel(Parcel out)
  3. 增加一個靜態的field,其實現 Parcelable.Creator 接口
  4. 創建一個 .aidl 文件,用以聲明你的 parcelables 類

Designing a Remote Interface Using AIDL 中,類 Rect 是一個不錯的示例,彌補了 Remote Service Binding 的不足。

二、調用 IPC 方法

萬事具備,只欠東風,IPC 備妥,只待調用。在 Remote Service Binding 中,RemoteServiceBinding 正是 IPC 的調用者,

既然要使用接口,那就先聲明 interface 類型的變量,

    /** The primary interface we will be calling on the service. */
    IRemoteService mService = null;
    /** Another interface we use on the service. */
    ISecondary mSecondaryService = null;

實現 ServiceConnection,在 onServiceConnected(ComponentName className, IBinder service) 中完成對 mServicemSecondaryService 的賦值。

    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className,
                IBinder service) {
            mService = IRemoteService.Stub.asInterface(service);
            // ... 以下省略
        }
    }

接着別忘了調用 Context.bindService(),完成任務以後,調用 Context.unbindService()。如果在 connection 中斷的情況下,調用 IPC 方法,你會遇到 DeadObjectException,這是 remote method 能拋出的唯一異常。

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