Android使用AIDL實現處理序間通訊,androidaidl
為使應用程式之間能夠彼此通訊,Android提供了IPC (Inter Process Communication,處理序間通訊)的一種獨特實現: AIDL (Android Interface Definition Language, Android介面定義語言)。
在Android中,每個應用(Application)執行在它自己的進程中,無法直接調用到其他應用的資源,這也符合“沙箱”的理念。因此,在Android中,當一個應用被執行時,一些操作是被限制的,比如訪問記憶體,訪問感應器,等等。這樣做可以最大化地保護系統,免得應用程式“為所欲為”。那我們有時需要在應用間互動,怎麼辦呢?於是,Android需要實現IPC協議。然而,這個協議還是有點複雜,主要因為需要實現資料管理系統(在進程或線程間傳遞資料)。為了暫時減緩這個麻煩,Android為我們實現了自己的IPC,也就是是AIDL 。
AIDL(Android Interface Definition Language)是一種介面定義語言,編譯器通過*.aidl檔案的描述資訊產生符合通訊協定的Java代碼,我們無需自己去寫這段繁雜的代碼,只需要在需要的時候調用即可,通過這種方式我們就可以完成進程間的通訊工作。
AIDL是IPC的一個輕量級實現,用了對於Java開發人員來說很熟悉的文法。Android也提供了一個工具,可以自動建立Stub(類構架,類骨架)。當我們需要在應用間通訊時,我們需要按以下幾步走:
1. 定義一個AIDL介面
2. 為遠程服務(Service)實現對應Stub
3. 將服務“暴露”給客戶程式使用
4. 用戶端調用
接下來,我就示範一個操作AIDL的最基本的流程。
一、建立工程
首先,我們需要建立一個服務端的工程,所以:
在com.scott.aidl這個包中,建立一個普通檔案(New->File),取名為IPerson.aidl。在這個檔案中定義了一個“問候”的方法:
package com.scott.aidl; interface IPerson { String greet(String someone); } 一旦檔案被儲存,Android的AIDL工具會在
gen/com/scott/aidl這個檔案夾裡自動產生對應的
IPerson.java這個檔案。因為是自動產生的,所以無需改動。這個檔案裡就包含了
Stub,我們接下來要為我們的遠程服務實現這個Stub。格式化後的代碼:
package com.scott.aidl; public interface IPerson extends android.os.IInterface { /** Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.scott.aidl.IPerson { private static final java.lang.String DESCRIPTOR = "com.scott.aidl.IPerson"; /** Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.scott.aidl.IPerson interface, * generating a proxy if needed. */ public static com.scott.aidl.IPerson asInterface(android.os.IBinder obj) { if ((obj == null)) { return null; } android.os.IInterface iin = (android.os.IInterface) obj.queryLocalInterface(DESCRIPTOR); if (((iin != null) && (iin instanceof com.scott.aidl.IPerson))) { return ((com.scott.aidl.IPerson) iin); } return new com.scott.aidl.IPerson.Stub.Proxy(obj); } public android.os.IBinder asBinder() { return this; } @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(DESCRIPTOR); return true; } case TRANSACTION_greet: { data.enforceInterface(DESCRIPTOR); java.lang.String _arg0; _arg0 = data.readString(); java.lang.String _result = this.greet(_arg0); reply.writeNoException(); reply.writeString(_result); return true; } } return super.onTransact(code, data, reply, flags); } private static class Proxy implements com.scott.aidl.IPerson { private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) { mRemote = remote; } public android.os.IBinder asBinder() { return mRemote; } public java.lang.String getInterfaceDescriptor() { return DESCRIPTOR; } public java.lang.String greet(java.lang.String someone) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.lang.String _result; try { _data.writeInterfaceToken(DESCRIPTOR); _data.writeString(someone); mRemote.transact(Stub.TRANSACTION_greet, _data, _reply, 0); _reply.readException(); _result = _reply.readString(); } finally { _reply.recycle(); _data.recycle(); } return _result; } } static final int TRANSACTION_greet = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); } public java.lang.String greet(java.lang.String someone) throws android.os.RemoteException; }
該檔案的大綱視圖如下:
IPerson介面中的抽象內部類Stub繼承android.os.Binder類並實現aidl檔案中的IPerson介面,比較重要的方法是asInterface(IBinder)方法,該方法會將IBinder類型的對象轉換成IPerson類型,必要的時候產生一個代理對象返回結果。
二、實現遠程服務
首先我們在com.scott.aidl這個包中建立一個類,取名叫AIDLService .java。
package com.scott.server; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import com.scott.aidl.IPerson; public class AIDLService extends Service { private static final String TAG = "AIDLService"; IPerson.Stub stub = new IPerson.Stub() { @Override public String greet(String someone) throws RemoteException { Log.i(TAG, "greet() called"); return "hello, " + someone; } }; @Override public IBinder onBind(Intent intent) { Log.i(TAG, "onBind() called"); return stub; } @Override public boolean onUnbind(Intent intent) { Log.i(TAG, "onUnbind() called"); return true; } @Override public void onDestroy() { super.onDestroy(); Log.i(TAG, "onDestroy() called"); } } 為了實現我們的服務,我們需要讓這個類中的
onBind方法返回一個
IBinder類的對象(stub的執行個體),這樣一來調用方擷取的IPerson.Stub就是我們的這個執行個體。這個
IBinder類的對象就代表了遠程服務的實現。為了實現這個服務,我們要用到自動產生的子類
IPerson.Stub。在其中,我們也必須實現我們之前在AIDL檔案中定義的greet方法。
當然,要想讓Service生效,我們還需要在AndroidManifest.xml中做一些配置工作,以便其他進程訪問:
<service android:name=".AIDLService"> <intent-filter> <action android:name="android.intent.action.AIDLService" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </service>
至此服務端已經完成。
三、用戶端調用
接下來我們就該完成用戶端的工作了。我已經建好了一個用戶端工程,
我們只需要把IPerson.aidl檔案拷到相應的目錄中即可,編譯器同樣會產生相對應的IPerson.java檔案,這一部分和服務端沒什麼區別。這樣一來,服務端和用戶端就在通訊協定上達到了統一。我們主要工作在MainActivity中完成。
package com.scott.client; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.Toast; import com.scott.aidl.IPerson; public class MainActivity extends Activity { private Button bindBtn; private Button greetBtn; private Button unbindBtn; private IPerson person; private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.i("ServiceConnection", "onServiceConnected() called"); person = IPerson.Stub.asInterface(service); } @Override public void onServiceDisconnected(ComponentName name) { //This is called when the connection with the service has been unexpectedly disconnected, //that is, its process crashed. Because it is running in our same process, we should never see this happen. Log.i("ServiceConnection", "onServiceDisconnected() called"); } }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); bindBtn = (Button) findViewById(R.id.bindBtn); bindBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent("android.intent.action.AIDLService"); bindService(intent, conn, Context.BIND_AUTO_CREATE); bindBtn.setEnabled(false); greetBtn.setEnabled(true); unbindBtn.setEnabled(true); } }); greetBtn = (Button) findViewById(R.id.greetBtn); greetBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try { String retVal = person.greet("scott"); Toast.makeText(MainActivity.this, retVal, Toast.LENGTH_SHORT).show(); } catch (RemoteException e) { Toast.makeText(MainActivity.this, "error", Toast.LENGTH_SHORT).show(); } } }); unbindBtn = (Button) findViewById(R.id.unbindBtn); unbindBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { unbindService(conn); bindBtn.setEnabled(true); greetBtn.setEnabled(false); unbindBtn.setEnabled(false); } }); } }
從代碼中可以看到,我們要重寫ServiceConnection中的onServiceConnected方法將IBinder類型的對像轉換成我們的IPerson類型。
到現在我們就剩下最後一個步驟了,這個環節也是最為關鍵的,就是綁定我們需要的服務。我們通過服務端Service定義的“android.intent.action.AIDLService”這個標識符來綁定其服務,這樣用戶端和服務端就實現了通訊的串連,我們就可以調用IPerson中的“問候”方法了。
四、結果展示
最後,貼幾張用戶端示範過程圖。
按照順序分別是:初始介面;點擊bindService後介面;點擊greet後介面;點擊unbindService後介面。
操作過程中的日誌如下:
五、總結
AIDL支援的資料類型與Java介面支援的資料類型有些不同:
1. 所有基礎類型(int, char, 等)
2. String,List,Map,CharSequence等類
3. 其他AIDL介面類型
4. 所有Parcelable的類
AIDL的最終效果就是讓 IPC的通訊就像調用函數那樣簡單。自動的幫你完成了參數序列化發送以及解析返回資料的那一系列麻煩。而你所需要做的就是寫上一個介面檔案,然後利用aidl工具轉化一下得到另一個java檔案,這個檔案在服務和用戶端程式各放一份。服務程式繼承IxxxxService.Stub 然後將函數介面裡面的邏輯代碼實現一下。