AIDL(Android Interface Definition Language) IPC機制是物件導向的,輕量級的。通過AIDL定義的介面可以實現伺服器端與用戶端的IPC通訊。在Android上,一個進程不能簡單的像訪問本進程記憶體一樣訪問其他進程的記憶體。所以,進程間想要對話,需要將對象拆解為作業系統可以理解的基本資料單元,並且有序的通過進程邊界。通過代碼來實現這個資料轉送過程是冗長乏味的,所幸的是android提供了AIDL工具來幫我們完成了此項工作。
注意:僅僅在你需要A應用程式的用戶端訪問B應用程式的伺服器端來實現IPC通訊,並且在伺服器端需要處理多線程(用戶端)訪問的情況下使用AIDL。如果不需要使用到進程間的IPC通訊,那麼通過Binder介面實現將更為合適,如果需要實現進程間的IPC通訊,但不需要處理多線程(多用戶端),通過Messager介面來實現將更為合適。不管怎樣,在使用AIDL之前,應先確保已理解了Bound Service。
AIDL介面的調用採用的是直接的函數調用方式,但你無法預知哪個進程(或線程)將調用該介面。同進程的線程調用和其他進程調用該介面之間是有所區別的:
- 在同進程中調用AIDL介面,AIDL介面代碼的執行將在調用該AIDL介面的線程中完成,如果在主UI線程中調用AIDL介面,那麼AIDL介面代碼的執行將會在這個主UI線程中完成。如果是其他線程,AIDL介面代碼的執行將在service中完成。因此,如果僅僅是本進程中的線程訪問該服務,你完全可以控制哪些線程將訪問這個服務(但是如果是這樣,那就完全沒必要使用AIDL了,而採取Binder介面的方式更為合適)。
- 遠程進程(其他線程)調用AIDL介面時,將會在AIDL所屬的進程的線程池中指派一個線程來執行該AIDL代碼,所以編寫AIDL時,你必須準備好可能有未知線程訪問、同一時間可能有多個調用發生(多個線程的訪問),所以ADIL介面的實現必須是安全執行緒的。
- 可以用關鍵字oneway來標明遠程調用的行為屬性,如果使用了該關鍵字,那麼遠程調用將僅僅是調用所需的資料轉送過來並立即返回,而不會等待結果的返回,也即是說不會阻塞遠程線程的運行。AIDL介面將最終將獲得一個從Binder線程池中產生的調用(和普通的遠程調用類似)。如果關鍵字oneway在本地調用中被使用,將不會對函數調用有任何影響。
定義AIDL介面
AIDL介面使用尾碼名位.aidl的檔案來定義,.aidl檔案使用java文法編寫,並且將該.aidl檔案儲存在 src/目錄下(無論是服務端還是用戶端都得儲存同樣的一份拷貝,也就是說只要是需要使用到該AIDL介面的應用程式都得在其src目錄下擁有一份.aidl檔案的拷貝)。
編譯時間,Android sdk 工具將會為 src/目錄下的.aidl檔案在 gen/ 目錄下產生一個IBinder介面。服務端必須相應的實現該IBinder介面。用戶端可以綁定該服務、調用其中的方法實現IPC通訊。
建立一個用AIDL實現的服務端,需要以下幾個步驟:
1. 建立.aidl檔案:
該檔案(YourInterface.aidl)定義了用戶端可用的方法和資料的介面
2. 實現這個介面:
Android SDK將會根據你的.aidl檔案產生AIDL介面。產生的介面包含一個名為Stub的抽象內部類,該類聲明了所有.aidl中描述的方法,你必須在代碼裡繼承該Stub類並且實現.aidl中定義的方法。
3.向用戶端公開服務端的介面:
實現一個Service,並且在onBinder方法中返回第2步中實現的那個Stub類的子類(實作類別)。
注意:
服務端AIDL的任何修改都必須的同步到所有的用戶端,否則用戶端調用服務端得介面可能會導致程式異常(因為此時用戶端此時可能會調用到服務端已不再支援的介面)。
1. 建立.aidl檔案
AIDL使用簡單的文法來聲明介面,描述其方法以及方法的參數和傳回值。這些參數和傳回值可以是任何類型,甚至是其他AIDL產生的介面。重要的是必須匯入所有非內建類型,哪怕是這些類型是在與介面相同的包中。
預設的AIDL支援一下的資料類型(這些類型不需要通過import匯入):
- java語言的未經處理資料類型(包括 int, long, char, boolen 等等)
- String
- CharSequence:該類是被TextView和其他控制項對象使用的字元序列
- List:列表中的所有元素必須是在此列出的類型,包括其他AIDL產生的介面和可打包類型。List可以像一般的類(例如List<String>)那樣使用,另一邊接收的具體類一般是一個ArrayList,這些方法會使用List介面
- Map:Map中的所有元素必須是在此列出的類型,包括其他AIDL產生的介面和可打包類型。一般的maps(例如Map<String,Integer>)不被支援,另一邊接收的具體類一般是一個HashMap,這些方法會使用Map介面。
對於其他的類型,在aidl中必須使用import匯入,即使該類型和aidl處於同一包內。
定義一個服務端介面時,注意一下幾點:
- 方法可以有0個或多個參數,可以使空傳回值也可以返回所需的資料。
- 所有非未經處理資料類型的參數必須指定參數方向(是傳入參數,還是傳出參數),傳入參數使用in關鍵字標記,傳出參數使用out,傳入傳出參數使用inout。如果沒有顯示的指定,那麼將預設使用in。
- 在aidl檔案中所有的注釋都將會包含在產生的IBinder介面中(在Import和pacakge語句之上的注釋除外)。
- aidl中只支援成員方法,不支援成員變數。
提示:
限定參數的傳輸方向非常有必要,因為編組(序列化)參數的代價非常昂貴。
下面是一個.aidl檔案的例子
// IRemoteService.aidlpackage com.example.android;// Declare any non-default types here with import statements/** Example service interface */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);}
將該.aidl檔案儲存在工程目錄中的 src/目錄下,當編譯產生apk時,sdk 工具將會在 gen/ 目錄下產生一個對應的IBiner介面的.java檔案。
如果使用eclipse編寫app,那麼這個IBinder介面檔案將會瞬間產生。如果不是使用eclipse,Ant 工具將會在下次編譯你的app時,產生這個IBinder介面檔案——所以當你編寫.aidl檔案之後應該立即使用 ant debug 編譯你的app,這樣你後續的代碼就可以使用這個已產生的IBinder介面類了。
2. 實現介面
產生的介面包含一個名為Stub的抽象的內部類,該類聲明了所有.aidl中描述的方法,
注意:
Stub還定義了少量的輔助方法,尤其是asInterface(),通過它或以獲得IBinder(當applicationContext.bindService()成功調用時傳遞到用戶端的onServiceConnected())並且返回用於調用IPC方法的介面執行個體,更多細節參見Calling an IPC Method。
要實現自己的介面,就從YourInterface.Stub類繼承,然後實現相關的方法(可以建立.aidl檔案然後實現stub方法而不用在中間編譯,Android編譯過程會在.java檔案之前處理.aidl檔案)。
下面的例子實現了對IRemoteService介面的調用,這裡使用了匿名對象。
private final IRemoteService.Stub mBinder = new IRemoteService.Stub() { public int getPid(){ return Process.myPid(); } public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) { // Does nothing }};
這樣,mBinder就是一個Stub類得對象,該對象為service提供了RPC介面。下一步中該對象將會向用戶端公開,這樣用戶端就可以通過該對象與該service進行互動了。
實現ADIL介面時需要注意一下幾點:
- 不能保證所有對aidl介面的調用都在主線程中執行,所以必須考慮多線程調用的情況,也就是必須考慮安全執行緒。
- 預設IPC調用是同步的。如果已知IPC服務端會花費很多毫秒才能完成,那就不要在Activity或View線程中調用,否則會引起應用程式掛起(Android可能會顯示“應用程式未響應”對話方塊),可以試著在獨立的線程中調用。
- 不會將異常返回給調用方
3. 向用戶端暴露介面
在完成了介面的實現後需要向用戶端暴露介面了,也就是發布服務,實現的方法是繼承 Service,然後實現以Service.onBind(Intent)返回一個實現了介面的類對象。下面的代碼錶示了暴露IRemoteService介面給用戶端的方式。
public class RemoteService extends Service { @Override public void onCreate() { super.onCreate(); } @Override public IBinder onBind(Intent intent) { // Return the interface return mBinder; } private final IRemoteService.Stub mBinder = new IRemoteService.Stub() { public int getPid(){ return Process.myPid(); } public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) { // Does nothing } };}
現在,如果用戶端(比如一個Activity)調用bindService()來串連該服務端(RemoteService) ,用戶端的onServiceConnected()回呼函數將會獲得從服務端(RemoteService )的onBind()返回的mBinder對象。
用戶端同樣得訪問該介面類(這裡指IRemoteService),所以,如果服務端和用戶端不在同一進程(應用程式)中,那麼用戶端也必須在 src/ 目錄下擁有和服務端同樣的一份.aidl檔案的拷貝(同樣是指,包名、類名、內容完全一模一樣),用戶端將會通過這個.aidl檔案產生android.os.Binder介面——以此來實現用戶端訪問AIDL中的方法。
當用戶端在onServiceConnected()回調方法中獲得IBinder對象後,必須通過調用YourServiceInterface.Stub.asInterface(service)將其轉化成為YourServiceInterface類型。例如:
IRemoteService mIRemoteService;private ServiceConnection mConnection = new ServiceConnection() { // Called when the connection with the service is established public void onServiceConnected(ComponentName className, IBinder service) { // Following the example above for an AIDL interface, // this gets an instance of the IRemoteInterface, which we can use to call on the service mIRemoteService = IRemoteService.Stub.asInterface(service); } // Called when the connection with the service disconnects unexpectedly public void onServiceDisconnected(ComponentName className) { Log.e(TAG, "Service has unexpectedly disconnected"); mIRemoteService = null; }};
更多的範例程式碼, 請看ApiDemos樣本中的RemoteService.java
.
4. 通過IPC傳遞對象
如果想要通過IPC介面將一個類從一個進程傳遞給另外一個進程,這個可以實現,但是必須得保證這個類在IPC兩端的有效性,並且該類必須支援Parcelable介面。支援Parcelable介面非常重要,因為這允許Android系統將Object拆解為未經處理資料類型,這樣才能達到跨進程封送(序列化發送)。
建立一個支援Parcelable協議的類,需要如下幾個步驟:
1. 使該類實現Parcelabel介面。
2. 實現public void writeToParcel(Parcel out) 方法,以便可以將對象的目前狀態寫入封裝對象中(Parcel)。
3. 在類中增加一個Parcelable.Creator介面的靜態對象CREATOR
。
4. 最後,建立AIDL檔案聲明這個可打包的類(見下文的Rect.aidl),如果使用的是自訂的編譯過程,那麼不要編譯此AIDL檔案,它像C語言的標頭檔一樣不需要編譯。
AIDL會使用這些方法的成員序列化和還原序列化對象。下面的代碼示範如何使Rect類支援序列化(parcelable)
package android.graphics;// Declare Rect so AIDL can find it and knows that it implements// the parcelable protocol.parcelable Rect;
下面的例子示範了如何讓Rect類實現Parcelable協議。
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(); }}
這裡Rect類序列化工作相當簡單,對可打包的其他類型的資料可以參見Parcel
類中的其他方法。
警告:不要忘了對從其他進程接收到的資料進行安全檢查。在上面的例子中,rect要從資料包中讀取4個數值,需要確認無論調用方想要做什麼,這些數值都是在可接受的範圍之內。想要瞭解更多的關於保持應用程式安全的內容,可參見 Security and Permissions。
5. 調用ICP方法
這裡給出調用遠端AIDL介面的步驟:
1. 在 src/ 目錄下包含.adil檔案。
2. 聲明一個IBinder
介面(通過.aidl檔案產生的)的執行個體。
3. 實現ServiceConnection
.
4. 調用Context.bindService()綁定你的ServiceConnection實作類別的對象(也就是遠程服務端)。
5. 在onServiceConnected()
方法中會接收到IBinder對象(也就是服務端),調用YourInterfaceName.Stub.asInterface((IBinder)service)
將傳回值轉換為YourInterface類型。
6. 調用介面中定義的方法,並且應該總是捕獲串連被打斷時拋出的DeadObjectException異常,這是遠端方法可能會拋出唯一異常。
7. 調用Context.unbindService()方法中斷連線。
這裡有幾個關於調用IPC服務的提示:
- 對象是在進程間會進行引用計數
- 可以發送匿名對象作為方法的參數
更多關於服務綁定的內容請看Bound Services相關文檔。
下面是AIDL-created服務的示範代碼,該代碼是從ApiDemos工程中的Remote Service中提取的。
public static class Binding extends Activity { /** The primary interface we will be calling on the service. */ IRemoteService mService = null; /** Another interface we use on the service. */ ISecondary mSecondaryService = null; Button mKillButton; TextView mCallbackText; private boolean mIsBound; /** * Standard initialization of this activity. Set up the UI, then wait * for the user to poke it before doing anything. */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.remote_service_binding); // Watch for button clicks. Button button = (Button)findViewById(R.id.bind); button.setOnClickListener(mBindListener); button = (Button)findViewById(R.id.unbind); button.setOnClickListener(mUnbindListener); mKillButton = (Button)findViewById(R.id.kill); mKillButton.setOnClickListener(mKillListener); mKillButton.setEnabled(false); mCallbackText = (TextView)findViewById(R.id.callback); mCallbackText.setText("Not attached."); } /** * Class for interacting with the main interface of the service. */ private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { // This is called when the connection with the service has been // established, giving us the service object we can use to // interact with the service. We are communicating with our // service through an IDL interface, so get a client-side // representation of that from the raw service object. mService = IRemoteService.Stub.asInterface(service); mKillButton.setEnabled(true); mCallbackText.setText("Attached."); // We want to monitor the service for as long as we are // connected to it. try { mService.registerCallback(mCallback); } catch (RemoteException e) { // In this case the service has crashed before we could even // do anything with it; we can count on soon being // disconnected (and then reconnected if it can be restarted) // so there is no need to do anything here. } // As part of the sample, tell the user what happened. Toast.makeText(Binding.this, R.string.remote_service_connected, Toast.LENGTH_SHORT).show(); } public void onServiceDisconnected(ComponentName className) { // This is called when the connection with the service has been // unexpectedly disconnected -- that is, its process crashed. mService = null; mKillButton.setEnabled(false); mCallbackText.setText("Disconnected."); // As part of the sample, tell the user what happened. Toast.makeText(Binding.this, R.string.remote_service_disconnected, Toast.LENGTH_SHORT).show(); } }; /** * Class for interacting with the secondary interface of the service. */ private ServiceConnection mSecondaryConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { // Connecting to a secondary interface is the same as any // other interface. mSecondaryService = ISecondary.Stub.asInterface(service); mKillButton.setEnabled(true); } public void onServiceDisconnected(ComponentName className) { mSecondaryService = null; mKillButton.setEnabled(false); } }; private OnClickListener mBindListener = new OnClickListener() { public void onClick(View v) { // Establish a couple connections with the service, binding // by interface names. This allows other applications to be // installed that replace the remote service by implementing // the same interface. bindService(new Intent(IRemoteService.class.getName()), mConnection, Context.BIND_AUTO_CREATE); bindService(new Intent(ISecondary.class.getName()), mSecondaryConnection, Context.BIND_AUTO_CREATE); mIsBound = true; mCallbackText.setText("Binding."); } }; private OnClickListener mUnbindListener = new OnClickListener() { public void onClick(View v) { if (mIsBound) { // If we have received the service, and hence registered with // it, then now is the time to unregister. if (mService != null) { try { mService.unregisterCallback(mCallback); } catch (RemoteException e) { // There is nothing special we need to do if the service // has crashed. } } // Detach our existing connection. unbindService(mConnection); unbindService(mSecondaryConnection); mKillButton.setEnabled(false); mIsBound = false; mCallbackText.setText("Unbinding."); } } }; private OnClickListener mKillListener = new OnClickListener() { public void onClick(View v) { // To kill the process hosting our service, we need to know its // PID. Conveniently our service has a call that will return // to us that information. if (mSecondaryService != null) { try { int pid = mSecondaryService.getPid(); // Note that, though this API allows us to request to // kill any process based on its PID, the kernel will // still impose standard restrictions on which PIDs you // are actually able to kill. Typically this means only // the process running your application and any additional // processes created by that app as shown here; packages // sharing a common UID will also be able to kill each // other's processes. Process.killProcess(pid); mCallbackText.setText("Killed service process."); } catch (RemoteException ex) { // Recover gracefully from the process hosting the // server dying. // Just for purposes of the sample, put up a notification. Toast.makeText(Binding.this, R.string.remote_call_failed, Toast.LENGTH_SHORT).show(); } } } }; // ---------------------------------------------------------------------- // Code showing how to deal with callbacks. // ---------------------------------------------------------------------- /** * This implementation is used to receive callbacks from the remote * service. */ private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() { /** * This is called by the remote service regularly to tell us about * new values. Note that IPC calls are dispatched through a thread * pool running in each process, so the code executing here will * NOT be running in our main thread like most other things -- so, * to update the UI, we need to use a Handler to hop over there. */ public void valueChanged(int value) { mHandler.sendMessage(mHandler.obtainMessage(BUMP_MSG, value, 0)); } }; private static final int BUMP_MSG = 1; private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case BUMP_MSG: mCallbackText.setText("Received from service: " + msg.arg1); break; default: super.handleMessage(msg); } } };}
註:由於譯者水平有限,若發現有何處不妥,還望指出,不甚感激,共同進步!
原文地址:http://developer.android.com/guide/developing/tools/aidl.html