解讀Android之Service(3)AIDL

來源:互聯網
上載者:User

標籤:android   aidl   ipc   

本文翻譯自android官方文檔,結合自己測試,整理如下。

Android Interface Definition Language(AIDL)能夠讓我們定義自己的編程介面,該介面可以使得用戶端和service之間進行跨進程通訊(interprocess communication,IPC)。通常,在android中無法直接跨進程通訊。因此,需要把傳遞的對象分解成系統可以識別的原始狀態(資料),並將它們跨進程式列化marshalling。由於marshalling過程繁瑣,因此android通過AIDL處理。

注意:只有當我們允許不同應用程式的用戶端擷取service來進行IPC,並且在service中需要處理多線程時,AIDL才是必須的。絕大多數應用程式都不應該用AIDL來建立Bound Service,因為這可能需要多執行緒能力並且會讓代碼變得更為複雜。因此,AIDL對絕大多數應用程式都不適用。如果只是在應用程式內部使用,並且不需要跨進程,我們可以通過繼承Binder類直接進行互動,這種是最常見的方式。若跨進程IPC且不需要處理多線程問題可以通過使用Messenger方法,因為Messenger把所有請求都放在一個線程中,因此不必擔心安全執行緒問題。

(下面的理解上還有些問題)在開始設計AIDL介面之前,需要注意的是調用AIDL介面是直接的調用方法,我們不應該假設調用方法發生在子線程。從本地進程和遠程進程中的線程調用是不同的:

  • 在本地進程中調用。若在主線程中調用,則AIDL介面會在主線程中執行。若是另外的子線程,則該線程執行service中的代碼。因此 若只有本地線程擷取service,我們能夠完全控制。這種情況不應該使用AIDL,而是繼承Binder實現。
  • 若是遠程進程調用,則AIDL的實現必須要保證完全地安全執行緒。這是因為若是支援遠程調用的話可能需要同時處理多個調用(並發處理)。
  • 單向改變遠程調用行為。當使用這種方式時,遠程調用不會阻塞;它簡單的發送資料,並立刻返回。最終,介面實現接收向常規的通過Binder線程池調用一樣處理遠程調用。若單向用在本地調用,則不會影響並且調用仍是非同步。

上述內容理解有的問題,須再查閱資料並驗證,同時請各位不吝賜教。

定義AIDL介面

AIDL介面必須定義在.aidl檔案中(命名滿足java文法),並同時儲存在service所在的應用程式和其它綁定該service的應用程式(需要通過AIDL進行IPC的service)中,儲存位置為原始碼中src/目錄下。

當我們建立一個.aidl檔案時,android SDK工具就會根據該檔案自動產生一個IBinder介面,並且儲存在gen/目錄下。service必須實現IBinder介面,用戶端才能綁定service並調用方法獲得該對象進行IPC。

上面的目錄是在eclipse中的,在android studio中則在:

為了能夠建立使用AIDL的service,必須要實現以下步驟:

  1. 建立.aidl檔案
    該檔案定義了帶有方法聲明的編程介面
  2. 實現介面
    android SDK工具使用java產生一個介面,依據是根據.aidl檔案。這個介面中有一個內部抽象類別Stub,該類繼承了Binder類,並且實現了AIDL介面中的方法,我們必須繼承Stub類和實現其方法。
  3. 將介面暴露給用戶端
    實現Service類,覆蓋onBind()方法,並且返回Stub的實現。

注意:在我們第一次發布之後的改變AIDL介面都要保證對原來的版本的相容性,避免其它應用無法訪問我們的service(其它用戶端可能拷貝的還是原來的介面版本)。

下面詳細介紹以上幾步:

1. 建立 .aidl檔案

AIDL需要滿足一些簡單的文法:能夠使我們聲明一個介面,該介面可以包含一個或多個方法,且能夠帶有參數和傳回值。參數和傳回值可以是任何類型的甚至是其它AIDL產生的介面。

每個.aidl檔案必須定義一個介面,並且只需要介面聲明和方法聲明。

預設情況下,AIDL支援以下資料類型:

  • java中八大基礎資料型別 (Elementary Data Type)
  • String
  • CharSequence
  • List
    當然,List中的類型也需要是AIDL支援的類型。通常使用List聲明變數,而具體的類型(例如ArrayList)在定義時確定。
  • Map
    和List用法一樣。

若是使用上述以外的類型(例如自訂類)必須匯入相關聲明,即使在同一個包中定義的。

在定義AIDL介面時,需要注意以下幾點:

  • 方法可以帶有0個或多個參數,可以選擇有無傳回值。
  • 所有的非基礎資料型別 (Elementary Data Type)都需要指定一個方向來標記資料的流向(進,出,進出同時)。基礎資料型別 (Elementary Data Type)預設是進,且不能改變。我們有必要限制資料方向(真正需要的方向),原因在於marshalling參數的代價消耗大。
  • 檔案中的所有代碼注釋都被包含在產生的IBinder介面中,除非是import和package之前注釋的。
  • 只支援方法,不支援靜態成員變數。且方法不能有修飾符。
  • 需要手動輸入包名(android studio不需要手動)

下面有一個例子:

package com.sywyg.servicetest;import com.sywyg.servicetest.Man;// Declare any non-default types here with import statements// 需要匯入自訂類型的位置interface IRemoteService{   /** Request the process ID of this service, to do evil things with it. */    int getPid();    /** Demonstrates some basic types that you can use as parameters     * and return values in AIDL.     */    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,              double aDouble, String aString);     // Man getMan();}

然後系統就會自動產生一個IRemoteService.java(對應IRemoteService.aidl)檔案。
有的編譯器是立刻產生,有的則在編譯應用程式時產生,這點注意。

2. 實現介面

自動組建檔案包含一個內部抽象類別Stub,繼承了Binder並是父類介面的一個抽象實現,實現了.aidl檔案中的所有方法。Stub同時定義了一些其他有用的方法,尤其是asInterface()方法,該方法接收一個IBinder對象,返回Stub介面的實現。Stub英文表示存根的意思,該類在服務端進程,我們必須繼承該類並實現aidl介面中的方法。

下面用一個匿名類實現簡單的介面執行個體:

    /**     * 定義一個匿名內部類實現aidl介面,需要繼承IRemoteService.Stub類     * 在.aidl檔案中聲明的方法需要在這裡實現具體功能     */    private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {        public int getPid(){            Log.d(TAG,"getPid is executed ...");            return android.os.Process.myPid();        }        public void basicTypes(int anInt, long aLong, boolean aBoolean,                                   float aFloat, double aDouble, String aString) {            Log.d(TAG,"basicTypes is executed ...");        }    };

Stub實現了Binder類(定義了遠端程序呼叫協議Remote Procedure Call Protocol RPC),因此mBinder可以傳輸給用戶端。

在實現AIDL時需要注意一下幾點:

  • 調用不能保證在主線程中執行,我們應該考慮多線程問題,並保證service是安全執行緒的。
  • 預設情況,RPC調用是非同步。若service需要長時間的操作要保證調用不能發生在主線程中,因為這個可能出現應用程式無法響應問題Application Not Responding ANR。因此我們應該保證調用發生在另外的子線程中。
  • 不會給調用者拋出異常。
3. 將介面暴露給用戶端

為service實現了AIDL介面,我們應該把介面暴露給用戶端,使得他們能夠綁定它。下面給出完整的代碼,說明如何?:

/** * 通過AIDL實現IPC * @author sywyg * @since 2015.7.16 */public class AIDLService extends Service {    private final String TAG = "result";    public AIDLService() {    }    /**     * 定義一個匿名內部類實現aidl介面,需要繼承IRemoteService.Stub類     * 在.aidl檔案中聲明的方法需要在這裡實現具體功能     */    private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {        public int getPid(){            Log.d(TAG,"getPid is executed ...");            return android.os.Process.myPid();        }        public void basicTypes(int anInt, long aLong, boolean aBoolean,                                   float aFloat, double aDouble, String aString) {            Log.d(TAG,"basicTypes is executed ...");        }    };    @Override    public IBinder onBind(Intent intent) {        return mBinder;    }}

那麼,當用戶端調用bindService()串連service時,用戶端回調onServiceConnected()方法接收mBinder執行個體(service的onBinder()方法返回的)。

用戶端必須也能夠獲得該介面類,因此當用戶端和service在不同的應用程式時,用戶端應用程式必須複製一份.aidl檔案,這樣才能獲得AIDL中的方法。

當客戶在onServiceConnected()方法中接收IBinder對象時,必須通過調用YourServiceInterface.Stub.asInterface(service)轉換為YourServiceInterface類型。如下:

IRemoteService mIRemoteService;  private ServiceConnection mConnection = new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            Log.d(TAG,"綁定成功");            // Following the example above for an AIDL interface,            // this gets an instance of the IRemoteService, which we can use to call on the service            // 還是接著上面的例子,通過這種方式獲得IRemoteService的一個執行個體,            // 這樣,我們可以在用戶端進行處理了。            mIRemoteService = IRemoteService.Stub.asInterface(service);            mBound = true;        }        @Override        public void onServiceDisconnected(ComponentName name) {            mBound = false;        }    };
通過IPC傳遞對象

我們可以實現通過IPC把對象從一個進程傳遞到另一個進程中。但是,我們必須要確保在另一個進程中可以獲得該對象(即需要該類的代碼),並且該類需要支援Parcelable介面。必須要支援Parcelable,這樣系統才能將對象分解為基礎資料型別 (Elementary Data Type)(能夠跨進程marshalled)。

注意:Parcelable是一個介面,實現該介面的類執行個體能夠儲存在Parcel中並從中恢複。該類中必須有一個名叫CREATOR的靜態成員變數,該成員是Parcelable.Creator的一個實現執行個體。

為了建立支援Parcelable協議的類,必須完成以下幾點:

  1. 該類必須實現Parcelable介面;
  2. 實現writeToParcel()和方法,記錄當前對象的狀態(成員變數等),並用Parcel儲存。還要實現describeContents(),一般返回0;
  3. 添加靜態成員變數CREAROR,該成員是Parcelable.Creator的一個實現執行個體;
  4. 最後建立一個.aidl檔案聲明該parcelable類(例如下面的Rect.aidl檔案)。若你進行中自訂產生過程,不要添加.aidl檔案,這是因為,它類似C語言的標頭檔,不會進行編譯的。???茫然

AIDL通過上述辦法產生marshall和unmarshall對象。

下面是一個實現Parcelable介面的類Rect,首先要有Rect.aidl檔案:

package android.graphics;// Declare Rect so AIDL can find it and knows that it implements// the parcelable protocol.// 聲明Rect,AIDL好找到並確認它實現了parcelable協議parcelable Rect;

下面是Rect類:

import android.os.Parcel;import android.os.Parcelable;public final class Rect implements Parcelable {    public int left;    public int top;    public int right;    public int bottom;    public static final Parcelable.Creator<Rect> CREATOR = newParcelable.Creator<Rect>() {        public Rect createFromParcel(Parcel in) {            return new Rect(in);        }        public Rect[] newArray(int size) {            return new Rect[size];        }    };    public Rect() {    }    private Rect(Parcel in) {        readFromParcel(in);    }    public void writeToParcel(Parcel out) {        out.writeInt(left);        out.writeInt(top);        out.writeInt(right);        out.writeInt(bottom);    }    public void readFromParcel(Parcel in) {        left = in.readInt();        top = in.readInt();        right = in.readInt();        bottom = in.readInt();    }}

Parcel同樣可以寫其它類型的資料。

警告:不要忘記從另一個進程中擷取資料時的安全問題。上面的例子,Rect擷取四個數,但是這取決於你獲得多少資料(讀寫順序要一致)。

調用IPC方法

用戶端必須完成以下步驟才能實現調用遠程介面:

  1. 在項目中包含.aidl檔案。
  2. 聲明一個IBinder對象執行個體(基於AIDL產生的)。
  3. 實現ServiceConnection。
  4. 調用Context.bindService(),並傳遞ServiceConnection的實現。
  5. onServiceConnected()實現中,我們可以接受IBinder的一個執行個體(名為service)。調用asInterface()轉換成介面執行個體。
  6. 調用在介面中定義的方法。必須要捕獲DeadObjectionException異常(當串連斷開時),這是調用遠程方法的唯一異常。
  7. 調用Context.unBindService()解除串連。

調用IPC service注意事項:

  • 對象是跨進程計數的參考型別,上一章講的一樣,可能會造成記憶體流失。
  • 能夠發送匿名對象作為方法參數。

解讀Android之Service(3)AIDL

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.