標籤: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,必須要實現以下步驟:
- 建立
.aidl檔案
該檔案定義了帶有方法聲明的編程介面
- 實現介面
android SDK工具使用java產生一個介面,依據是根據.aidl檔案。這個介面中有一個內部抽象類別Stub,該類繼承了Binder類,並且實現了AIDL介面中的方法,我們必須繼承Stub類和實現其方法。
- 將介面暴露給用戶端
實現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協議的類,必須完成以下幾點:
- 該類必須實現Parcelable介面;
- 實現
writeToParcel()和方法,記錄當前對象的狀態(成員變數等),並用Parcel儲存。還要實現describeContents(),一般返回0;
- 添加靜態成員變數CREAROR,該成員是Parcelable.Creator的一個實現執行個體;
- 最後建立一個
.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方法
用戶端必須完成以下步驟才能實現調用遠程介面:
- 在項目中包含
.aidl檔案。
- 聲明一個IBinder對象執行個體(基於AIDL產生的)。
- 實現ServiceConnection。
- 調用
Context.bindService(),並傳遞ServiceConnection的實現。
- 在
onServiceConnected()實現中,我們可以接受IBinder的一個執行個體(名為service)。調用asInterface()轉換成介面執行個體。
- 調用在介面中定義的方法。必須要捕獲DeadObjectionException異常(當串連斷開時),這是調用遠程方法的唯一異常。
- 調用
Context.unBindService()解除串連。
調用IPC service注意事項:
- 對象是跨進程計數的參考型別,上一章講的一樣,可能會造成記憶體流失。
- 能夠發送匿名對象作為方法參數。
解讀Android之Service(3)AIDL