本文例子中的原始碼地址: Github:程式間通訊之AIDL
AIDL(Android 介面定義語言)是定義客戶端與服務使用程式間通訊 (IPC) 進行相互通訊時都認可的程式設計介面,一個程式通常無法訪問另一個程式的記憶體,但是可以通過AIDL進行程式間通訊。**AIDL可以讓客戶端以IPC(程式間通訊)方式訪問服務端,並且服務端可以處理多執行緒;如果不需要處理多執行緒,則可以使用Messenger類來實現介面;如果只是需要本地的Service,不需要IPC過程,則只需要通過實現一個Binder類就可以了。**使用AIDL的前提必須瞭解繫結Service,Service為我們建立Binder驅動,Binder驅動是服務端與客戶端通訊的橋樑。AIDL通過我們寫的.aidl檔案,生成了一個介面,一個Stub類用於服務端,一個Proxy類用於客戶端,不熟悉Service的可以看下官方文件:Service
定義AIDL介面步驟:
1.建立.aidl檔案 在服務端的src/ 目錄內建立.aidl型別的檔案,如果客戶端和服務端不在一個應用內,則需要將.aidl檔案複製到客戶端。 .aidl檔案中可以定義一個或多個介面方法,方法引數和返回值可以是任意型別的,預設情況下,AIDL支援下面的幾種型別:
- Java 程式語言中的所有原語型別(如 int、long、char、boolean 等等)
- String
- CharSequence
- List (List中的所有元素都必須是以上列表中支援的資料型別、其他 AIDL 生成的介面或宣告的可打包型別。 可選擇將List用作“通用”類(如List)。另一端實際接收的具體類始終是ArrayList,但生成的方法使用的是List介面)
- Map (Map中的所有元素都必須是以上列表中支援的資料型別、其他 AIDL 生成的介面或宣告的可打包型別。 不支援通用 Map(如 Map<String,Integer>形式的 Map)。 另一端實際接收的具體類始終是HashMap,但生成的方法使用的是 Map介面)
注意:如果在.aidl檔案中使用的是自定義物件,即使此型別檔案和.aidl檔案在同一個資料夾中,也必須使用import語句匯入,並且必須實現Parcelable介面。另外,非原語型別的引數需要指示資料走向的方向標記,可以是 in(輸入)、out(輸出) 或 inout(輸入輸出),預設是in,一定要正確規定方向,因為編組引數的開銷極大。
比如宣告一個自定義物件Rect.java:
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 = new
Parcelable.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();
}
}
複製程式碼
建立一個宣告可打包類的 .aidl 檔案,與宣告的自定義物件同名,並且這兩個同名的類和.aidl檔案放在同一個包下,如 Rect.aidl:
package android.graphics;
//注意這裡是小寫的parcelable來宣告
parcelable Rect;
複製程式碼
上面把Rect類放在.aidl目錄中時,編譯會提示找不到這個類,因為Android Studio預設會去java目錄下找,需要在build.gradle檔案 android{ } 中間增加一段程式碼,讓aidl目錄裡面的java檔案也能被識別:
sourceSets{
main{
java.srcDirs=['src/main/java','src/main/aidl']
}
}
複製程式碼
以下是一個 IRemoteService.aidl 檔案示例:
// IRemoteService.aidl
package 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);
}
複製程式碼
2.實現介面 SDK會根據.aidl檔案自動生成一個介面,介面中有一個名為Stub的內部抽象類,用於擴充套件Binder類並實現AIDL中的方法,服務端需要覆寫Stub類並實現方法。此外,Stub類中還有一個asInterface()方法,該方法帶IBinder(通常便是傳給客戶端 onServiceConnected()回撥方法的引數)並返回存根介面例項。
以下是一個使用匿名例項實現名為 IRemoteService 的介面(由以上 IRemoteService.aidl 示例定義)的示例:
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類的一個例項(一個 Binder),用於定義服務的 RPC 介面。 在下一步中,將向客戶端公開該例項,以便客戶端能與服務進行互動。 在實現 AIDL 介面時應注意遵守以下這幾個規則:
- 由於不能保證在主執行緒上執行傳入呼叫,因此一開始就需要做好多執行緒處理準備,並將您的服務正確地編譯為執行緒安全服務。
- 預設情況下,RPC 呼叫是同步呼叫。如果明知服務完成請求的時間不止幾毫秒,就不應該從 Activity 的主執行緒呼叫服務,因為這樣做可能會使應用掛起(Android 可能會顯示“Application is Not Responding”對話方塊)— 您通常應該從客戶端內的單獨執行緒呼叫服務。
- 引發的任何異常都不會回傳給呼叫方。
3.向客戶端公開介面 服務端實現Service並重寫Onbind()以返回Stub類的實現,客戶端就能與服務進行互動了。 以下是一個向客戶端公開 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()以連線此服務時,客戶端的onServiceConnected()回撥會接收服務的onBind()方法返回的 mBinder例項。 客戶端還必須具有對 interface 類的訪問許可權,因此如果客戶端和服務在不同的應用內,則客戶端的應用 src/目錄內必須包含 .aidl檔案(它生成android.os.Binder介面 — 為客戶端提供對 AIDL 方法的訪問許可權)的副本。 當客戶端在onServiceConnected()回撥中收到IBinder時,它必須呼叫 YourServiceInterface.Stub.asInterface(service)以將返回的引數轉換成YourServiceInterface型別。例如:
IRemoteService mIRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mIRemoteService = IRemoteService.Stub.asInterface(service);
}
public void onServiceDisconnected(ComponentName className) {
Log.e(TAG, "Service has unexpectedly disconnected");
mIRemoteService = null;
}
};
複製程式碼
客戶端執行IPC過程:
- 在專案 src/目錄中加入 .aidl檔案。
- 宣告一個IBinder介面例項(基於 AIDL 生成)。
- 實現 ServiceConnection。
- 呼叫 Context.bindService(),以傳入ServiceConnection實現。
- 在您的 onServiceConnected()實現中,將收到一個 IBinder例項(名為service)。呼叫YourInterfaceName.Stub.asInterface((IBinder)service) ,以將返回的引數轉換為 YourInterface 型別。
- 呼叫在介面上定義的方法。始終捕獲 DeadObjectException異常,它們是在連線中斷時引發的;這將是遠端方法引發的唯一異常。
- 如需斷開連線,請使用您的介面例項呼叫 Context.unbindService()。
上面說的是整個流程,下面來舉個例子,先看效果圖:
服務端程式碼
Apple.aidl:
package org.ninetripods.mq.multiprocess_sever;
parcelable Apple;
複製程式碼
其中Apple類已經實現Parcelable介面了,這裡就不再貼出來了,接著宣告IAidlCallBack.aidl 提供客戶端呼叫的方法:
// IAidlCallBack.aidl
package org.ninetripods.mq.multiprocess_sever;
// Declare any non-default types here with import statements
import org.ninetripods.mq.multiprocess_sever.Apple;
interface IAidlCallBack {
Apple getAppleInfo();
}
複製程式碼
然後編寫RemoteService.java類:
public class RemoteService extends Service {
public RemoteService() {
}
@Override
public IBinder onBind(Intent intent) {
//通過onBind()方法返回Binder例項供客戶端呼叫
return mBinder;
}
private IAidlCallBack.Stub mBinder = new IAidlCallBack.Stub() {
@Override
public Apple getAppleInfo() throws RemoteException {
return new Apple("蛇果", 20f, getString(R.string.respose_info));
}
};
}
複製程式碼
最後在AndroidManifest.xml裡宣告一下:
<service
android:name=".RemoteService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.mq.common.service" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>
複製程式碼
客戶端程式碼:AidlActivity.java
首先實現ServiceConnection,在onServiceConnected()中通過IAidlCallBack.Stub.asInterface(service) 拿到Binder例項,然後就可以呼叫服務端方法了,程式碼如下:
private ServiceConnection mCommonConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
isCommonBound = true;
mCommonService = IAidlCallBack.Stub.asInterface(service);
if (mCommonService != null) {
try {
Apple apple = mCommonService.getAppleInfo();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
isCommonBound = false;
mCommonService = null;
showMessage("\n連線遠端服務端失敗...");
}
};
複製程式碼
最後是繫結服務:
Intent commonIntent = new Intent();
commonIntent.setAction("android.mq.common.service");
commonIntent.setPackage("org.ninetripods.mq.multiprocess_sever");
bindService(commonIntent, mCommonConnection, Context.BIND_AUTO_CREATE);
複製程式碼
例子中還有個觀察者模式,跟在同一程式中使用觀察者模式是有區別的,要用到RemoteCallbackList,具體使用方法程式碼中有,就不再詳述了~ 恩~整個過程差不多是這樣了,完整程式碼已上傳至 Github:程式間通訊之AIDL,如果對您有幫助,給個star吧,感激不盡~(注:專案中有兩個專案,請確保先啟動Server端,否則看不到效果)