[文章內(nèi)容來自Developers]
AIDL(Android 接口定義語言)與您可能使用過的其他 IDL 類似。 您可以利用它定義客戶端與服務(wù)使用進(jìn)程間通信 (IPC) 進(jìn)行相互通信時都認(rèn)可的編程接口。 在 Android 上,一個進(jìn)程通常無法訪問另一個進(jìn)程的內(nèi)存。 盡管如此,進(jìn)程需要將其對象分解成操作系統(tǒng)能夠識別的原語,并將對象編組成跨越邊界的對象。 編寫執(zhí)行這一編組操作的代碼是一項繁瑣的工作,因此 Android 會使用 AIDL 來處理。
注:只有允許不同應(yīng)用的客戶端用 IPC 方式訪問服務(wù),并且想要在服務(wù)中處理多線程時,才有必要使用 AIDL。 如果您不需要執(zhí)行跨越不同應(yīng)用的并發(fā) IPC,就應(yīng)該通過實現(xiàn)一個 Binder創(chuàng)建接口;或者,如果您想執(zhí)行 IPC,但根本不需要處理多線程,則使用 Messenger 類來實現(xiàn)接口。無論如何,在實現(xiàn) AIDL 之前,請您務(wù)必理解綁定服務(wù)。
在您開始設(shè)計 AIDL 接口之前,要注意 AIDL 接口的調(diào)用是直接函數(shù)調(diào)用。 您不應(yīng)該假設(shè)發(fā)生調(diào)用的線程。 視調(diào)用來自本地進(jìn)程還是遠(yuǎn)程進(jìn)程中的線程,實際情況會有所差異。 具體而言:
- 來自本地進(jìn)程的調(diào)用在發(fā)起調(diào)用的同一線程內(nèi)執(zhí)行。如果該線程是您的主 UI 線程,則該線程繼續(xù)在 AIDL 接口中執(zhí)行。 如果該線程是其他線程,則其便是在服務(wù)中執(zhí)行您的代碼的線程。 因此,只有在本地線程訪問服務(wù)時,您才能完全控制哪些線程在服務(wù)中執(zhí)行(但如果真是這種情況,您根本不應(yīng)該使用 AIDL,而是應(yīng)該通過實現(xiàn) Binder 類創(chuàng)建接口)。
- 來自遠(yuǎn)程進(jìn)程的調(diào)用分派自平臺在您的自有進(jìn)程內(nèi)部維護(hù)的線程池。 您必須為來自未知線程的多次并發(fā)傳入調(diào)用做好準(zhǔn)備。 換言之,AIDL 接口的實現(xiàn)必須是完全線程安全實現(xiàn)。
- oneway關(guān)鍵字用于修改遠(yuǎn)程調(diào)用的行為。使用該關(guān)鍵字時,遠(yuǎn)程調(diào)用不會阻塞;它只是發(fā)送事務(wù)數(shù)據(jù)并立即返回。接口的實現(xiàn)最終接收此調(diào)用時,是以正常遠(yuǎn)程調(diào)用形式將其作為來自 Binder
線程池的常規(guī)調(diào)用進(jìn)行接收。 如果 oneway
用于本地調(diào)用,則不會有任何影響,調(diào)用仍是同步調(diào)用。
定義 AIDL 接口
您必須使用 Java 編程語言語法在 .aidl文件中定義 AIDL 接口,然后將它保存在托管服務(wù)的應(yīng)用以及任何其他綁定到服務(wù)的應(yīng)用的源代碼(src/目錄)內(nèi)。
您開發(fā)每個包含 .aidl文件的應(yīng)用時,Android SDK 工具都會生成一個基于該 .aidl文件的 IBinder接口,并將其保存在項目的 gen/目錄中。服務(wù)必須視情況實現(xiàn) IBinder接口。然后客戶端應(yīng)用便可綁定到該服務(wù),并調(diào)用 IBinder中的方法來執(zhí)行 IPC。
如需使用 AIDL 創(chuàng)建綁定服務(wù),請執(zhí)行以下步驟:
- 創(chuàng)建 .aidl 文件
此文件定義帶有方法簽名的編程接口。 - 實現(xiàn)接口
Android SDK 工具基于您的 .aidl文件,使用 Java 編程語言生成一個接口。此接口具有一個名為 Stub 的內(nèi)部抽象類,用于擴展 Binder類并實現(xiàn) AIDL 接口中的方法。您必須擴展 Stub類并實現(xiàn)方法。 - 向客戶端公開該接口
實現(xiàn) Service并重寫 onBind()以返回 Stub類的實現(xiàn)。
注意:在 AIDL 接口首次發(fā)布后對其進(jìn)行的任何更改都必須保持向后兼容性,以避免中斷其他應(yīng)用對您的服務(wù)的使用。 也就是說,因為必須將您的 .aidl文件復(fù)制到其他應(yīng)用,才能讓這些應(yīng)用訪問您的服務(wù)的接口,因此您必須保留對原始接口的支持。
1. 創(chuàng)建 .aidl 文件
AIDL 使用簡單語法,使您能通過可帶參數(shù)和返回值的一個或多個方法來聲明接口。 參數(shù)和返回值可以是任意類型,甚至可以是其他 AIDL 生成的接口。
您必須使用 Java 編程語言構(gòu)建 .aidl文件。每個 .aidl文件都必須定義單個接口,并且只需包含接口聲明和方法簽名。
默認(rèn)情況下,AIDL 支持下列數(shù)據(jù)類型:
- Java 編程語言中的所有原語類型(如 int、long、char、boolean等等)
- String
- CharSequence
- List
List中的所有元素都必須是以上列表中支持的數(shù)據(jù)類型、其他 AIDL 生成的接口或您聲明的可打包類型。 可選擇將 List 用作“通用”類(例如,List<String>)。另一端實際接收的具體類始終是 ArrayList,但生成的方法使用的是 List接口。 - Map
Map中的所有元素都必須是以上列表中支持的數(shù)據(jù)類型、其他 AIDL 生成的接口或您聲明的可打包類型。 不支持通用 Map(如Map<String,Integer>形式的 Map)。 另一端實際接收的具體類始終是 HashMap,但生成的方法使用的是 Map 接口。
您必須為以上未列出的每個附加類型加入一個 import語句,即使這些類型是在與您的接口相同的軟件包中定義。
定義服務(wù)接口時,請注意:
- 方法可帶零個或多個參數(shù),返回值或空值。
所有非原語參數(shù)都需要指示數(shù)據(jù)走向的方向標(biāo)記。可以是 in、out或 inout(見以下示例)。原語默認(rèn)為 in,不能是其他方向。
注意:您應(yīng)該將方向限定為真正需要的方向,因為編組參數(shù)的開銷極大。
- .aidl文件中包括的所有代碼注釋都包含在生成的 IBinder接口中(import 和 package 語句之前的注釋除外)
- 只支持方法;您不能公開 AIDL 中的靜態(tài)字段。
以下是一個 .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/目錄內(nèi),當(dāng)您開發(fā)應(yīng)用時,SDK 工具會在項目的 gen/目錄中生成 IBinder接口文件。生成的文件名與 .aidl文件名一致,只是使用了 .java擴展名(例如,IRemoteService.aidl生成的文件名是 IRemoteService.java)。
如果您使用 Android Studio,增量編譯幾乎會立即生成 Binder 類。 如果您不使用 Android Studio,則 Gradle 工具會在您下一次開發(fā)應(yīng)用時生成 Binder 類 — 您應(yīng)該在編寫完 .aidl文件后立即用 gradle assembleDebug(或 gradle assembleRelease)編譯項目,以便您的代碼能夠鏈接到生成的類。
2.實現(xiàn)接口
當(dāng)您開發(fā)應(yīng)用時,Android SDK 工具會生成一個以 .aidl文件命名的 .java接口文件。生成的接口包括一個名為 Stub的子類,這個子類是其父接口(例如,YourInterface.Stub)的抽象實現(xiàn),用于聲明 .aidl文件中的所有方法。
注:Stub還定義了幾個幫助程序方法,其中最引人關(guān)注的是 asInterface(),該方法帶 IBinder(通常便是傳遞給客戶端 onServiceConnected()回調(diào)方法的參數(shù))并返回存根接口實例。 如需了解如何進(jìn)行這種轉(zhuǎn)換的更多詳細(xì)信息,請參見調(diào)用 IPC 方法。
如需實現(xiàn) .aidl生成的接口,請擴展生成的 Binder接口(例如,YourInterface.Stub)并實現(xiàn)從 .aidl文件繼承的方法。
以下是一個使用匿名實例實現(xiàn)名為 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
}
};
現(xiàn)在,mBinder是 Stub類的一個實例(一個 Binder),用于定義服務(wù)的 RPC 接口。 在下一步中,將向客戶端公開該實例,以便客戶端能與服務(wù)進(jìn)行交互。
在實現(xiàn) AIDL 接口時應(yīng)注意遵守以下這幾個規(guī)則:
- 由于不能保證在主線程上執(zhí)行傳入調(diào)用,因此您一開始就需要做好多線程處理準(zhǔn)備,并將您的服務(wù)正確地編譯為線程安全服務(wù)。
- 默認(rèn)情況下,RPC 調(diào)用是同步調(diào)用。如果您明知服務(wù)完成請求的時間不止幾毫秒,就不應(yīng)該從 Activity 的主線程調(diào)用服務(wù),因為這樣做可能會使應(yīng)用掛起(Android 可能會顯示“Application is Not Responding”對話框)— 您通常應(yīng)該從客戶端內(nèi)的單獨線程調(diào)用服務(wù)。
- 您引發(fā)的任何異常都不會回傳給調(diào)用方。
** 3.向客戶端公開該接口**
您為服務(wù)實現(xiàn)該接口后,就需要向客戶端公開該接口,以便客戶端進(jìn)行綁定。 要為您的服務(wù)公開該接口,請擴展 Service并實現(xiàn) onBind(),以返回一個類實例,這個類實現(xiàn)了生成的 Stub(見前文所述)。以下是一個向客戶端公開 IRemoteService
示例接口的服務(wù)示例。
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
}
};
}
現(xiàn)在,當(dāng)客戶端(如 Activity)調(diào)用 bindService() 以連接此服務(wù)時,客戶端的 onServiceConnected()回調(diào)會接收服務(wù)的 onBind()方法返回的 mBinder實例。
客戶端還必須具有對 interface 類的訪問權(quán)限,因此如果客戶端和服務(wù)在不同的應(yīng)用內(nèi),則客戶端的應(yīng)用 src/目錄內(nèi)必須包含 .aidl文件(它生成 android.os.Binder接口 — 為客戶端提供對 AIDL 方法的訪問權(quán)限)的副本。
當(dāng)客戶端在 onServiceConnected()回調(diào)中收到 IBinder時,它必須調(diào)用 YourServiceInterface.Stub.asInterface(service)
以將返回的參數(shù)轉(zhuǎn)換成 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
類。
通過 IPC 傳遞對象
通過 IPC 接口把某個類從一個進(jìn)程發(fā)送到另一個進(jìn)程是可以實現(xiàn)的。 不過,您必須確保該類的代碼對 IPC 通道的另一端可用,并且該類必須支持Parcelable 接口。支持 Parcelable接口很重要,因為 Android 系統(tǒng)可通過它將對象分解成可編組到各進(jìn)程的原語。
如需創(chuàng)建支持 Parcelable協(xié)議的類,您必須執(zhí)行以下操作:
- 讓您的類實現(xiàn) Parcelable接口。
- 實現(xiàn) writeToParcel,它會獲取對象的當(dāng)前狀態(tài)并將其寫入 Parcel。
- 為您的類添加一個名為 CREATOR的靜態(tài)字段,這個字段是一個實現(xiàn) Parcelable.Creator接口的對象。
- 最后,創(chuàng)建一個聲明可打包類的 .aidl文件(按照下文 Rect.aidl文件所示步驟)。如果您使用的是自定義編譯進(jìn)程,切勿**在您的編譯中添加 .aidl文件。 此 .aidl文件與 C 語言中的頭文件類似,并未編譯。
AIDL 在它生成的代碼中使用這些方法和字段將您的對象編組和取消編組。
例如,以下這個 Rect.aidl文件可創(chuàng)建一個可打包的 Rect類:
package android.graphics;
// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect;
以下示例展示了 Rect類如何實現(xiàn) Parcelable協(xié)議。
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
類中的編組相當(dāng)簡單。看一看 Parcel上的其他方法,了解您可以向 Parcel 寫入哪些其他類型的值。
警告:別忘記從其他進(jìn)程接收數(shù)據(jù)的安全影響。 在本例中,Rect從 Parcel讀取四個數(shù)字,但要由您來確保無論調(diào)用方目的為何這些數(shù)字都在相應(yīng)的可接受值范圍內(nèi)。
調(diào)用 IPC 方法
調(diào)用類必須執(zhí)行以下步驟,才能調(diào)用使用 AIDL 定義的遠(yuǎn)程接口:
- 在項目 src/目錄中加入 .aidl文件。
- 聲明一個 IBinder 接口實例(基于 AIDL 生成)。
- 實現(xiàn) ServiceConnection。
- 調(diào)用 Context.bindService(),以傳入您的 ServiceConnection實現(xiàn)。
- 在您的 onServiceConnected()實現(xiàn)中,您將收到一個 IBinder實例(名為 service)。調(diào)用YourInterfaceName.Stub.asInterface((IBinder)service)
,以將返回的參數(shù)轉(zhuǎn)換為 YourInterface 類型。 - 調(diào)用您在接口上定義的方法。您應(yīng)該始終捕獲 DeadObjectException異常,它們是在連接中斷時引發(fā)的;這將是遠(yuǎn)程方法引發(fā)的唯一異常。
- 如需斷開連接,請使用您的接口實例調(diào)用 Context.unbindService()。
有關(guān)調(diào)用 IPC 服務(wù)的幾點說明:
- 對象是跨進(jìn)程計數(shù)的引用。
- 您可以將匿名對象作為方法參數(shù)發(fā)送。
如需了解有關(guān)綁定到服務(wù)的詳細(xì)信息,請閱讀綁定服務(wù)文檔。
以下這些示例代碼摘自 ApiDemos 項目的遠(yuǎn)程服務(wù)示例代碼,展示了如何調(diào)用 AIDL 創(chuàng)建的服務(wù)。
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.
Intent intent = new Intent(Binding.this, RemoteService.class);
intent.setAction(IRemoteService.class.getName());
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
intent.setAction(ISecondary.class.getName());
bindService(intent, 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);
}
}
};
}