Android Interface Definition Language (AIDL)
注:本文翻譯自https://developer.android.com/guide/components/aidl.html
AIDL(Android Interface Definition Language)跟你可能使用的其他接口定義語言類似硬纤。它允許你定義一個編程接口,這個接口是的客戶端與服務(wù)端都約定一致用來跨進程通信赃磨。在Android系統(tǒng)中筝家,一個進程通常不能訪問另一個進程的內(nèi)存。所以他們需要將他們的對象拆解成操作系統(tǒng)所能識別的原語數(shù)據(jù)(primitives)邻辉,然后傳入到另一個進程之后再替你組裝成對象溪王。而這個組裝操作的代碼寫起來十分冗長(tedious ),因此Android通過采用AIDL來替你處理這一冗長的操作值骇。
注意:只有當(dāng)你允許讓來自不同應(yīng)用的客戶端為了跨進程通信(IPC)訪問你的服務(wù)并且在你的服務(wù)中有多個線程需要處理莹菱,才有必要使用AIDL。如果沒有必要處理并發(fā)跨進程(IPC)訪問不同的應(yīng)用吱瘩,你應(yīng)該通過實現(xiàn)Binder接來創(chuàng)建接口道伟,或者如果你想執(zhí)行跨進程通信,但又沒有必要處理多線程, 使用Messager 來實現(xiàn)接口蜜徽。不管怎樣祝懂,在實現(xiàn)一個AIDL接口之前請確保你已經(jīng)理解了 綁定服務(wù)。
在你開始定義你的AIDL接口之前拘鞋,要意識到對一個AIDL接口的調(diào)用時一個直接的函數(shù)調(diào)用砚蓬。你不能假定這個調(diào)用是在哪一個線程。產(chǎn)生的結(jié)果取決于調(diào)用該方法的線程是位于本地進程還是遠程進程盆色,特別是:
- 如果調(diào)用是來自本地進程并且跟調(diào)用執(zhí)行與同一個線程灰蛙,比如是你的UI主線程,這個線程執(zhí)行繼續(xù)執(zhí)行AIDL接口隔躲。如果是另一個線程摩梧,那就是在service中執(zhí)行的線程。因此蹭越,僅當(dāng)本地線程訪問服務(wù)的障本,你才能完全控制AIDL接口在那個線程執(zhí)行(但是如果是這種情形,你完全不行該使用AIDL响鹃,而是通過實現(xiàn)Binder創(chuàng)建接口)
- 如果對AIDL方法的調(diào)用來自遠程的進程驾霜,并且這個遠程的進程采用線程池(這個線程池是在你自己的進程中維護)來分發(fā)對該AIDL方法的調(diào)用,你必須為即將到來的未知線程做準備买置,這些線程使得同時有多個調(diào)用在進行的猴誊。換句話說死相,你的AIDL的方法的實現(xiàn)必須完全線程安全宣吱。
- 單向(
oneway
)關(guān)鍵字修改遠程調(diào)用长豁。一旦使用,遠程調(diào)用不會阻塞轩触,它只是簡單地發(fā)送傳輸數(shù)據(jù)并且立即返回寞酿。接口的實現(xiàn)最終收到一個來于Binder線程池被當(dāng)做一般的遠程調(diào)用的常規(guī)的調(diào)用。如果單向(oneway
)采用本地調(diào)用脱柱,則不會有什么影響并且調(diào)用時同步的伐弹。
定義AIDL接口
你必須在一個以.aidl
為后綴的文件中采用Java的語法來定義AIDL接口,然后將它保存在提供該服務(wù)和其他需要綁定該服務(wù)的應(yīng)用的源代碼目錄src/
下面榨为。
當(dāng)你構(gòu)造沒有應(yīng)用包含有.aidl
格式文件惨好,Android的SDK工具就會根據(jù).aidl
文件生成一個 IBinder接口并將它保存在項目的gen/
目錄下面。服務(wù)必須根據(jù)需要實現(xiàn)這個 IBinder随闺。然后客戶端綁定該服務(wù)調(diào)用這個 IBinder中的方法來執(zhí)行跨進程通信日川。
要創(chuàng)建一個通過AIDL綁定的服務(wù),采用下面的步驟矩乐。
- 創(chuàng)建.aidl文件
這個文件定義了帶有方法簽名的接口 - 實現(xiàn)該接口
Android SDK工具會根據(jù)你的.aidl
文件以Java編程語言生成一個接口龄句。該接口有一個叫做Stub
的內(nèi)部抽象類, 它繼承自Binder并且實現(xiàn)了你在AIDL接口中定義的方法。你必須繼承Stub
類然后實現(xiàn)它的方法撒璧。 - 暴露接口給客戶端
實現(xiàn)一個 Service并且覆蓋 onBind() 方法透葛,返回你針對Stub
類的實現(xiàn).
注意:在你第一次發(fā)布服務(wù)之后,每當(dāng)你的AIDL接口所有改變卿樱,你需要保持后向兼容,以避免破壞其他應(yīng)用使用你的服務(wù)硫椰。 因為你的.aidl文件必須拷貝到其他應(yīng)用中以便讓其他的應(yīng)用訪問你的服務(wù)接口繁调。你必須維護隊原始接口的支持。
1靶草、創(chuàng)建.aidl文件
AIDL使用簡單的語法讓你申明一個帶有一個或者多個可以有參數(shù)和返回值的方法蹄胰。參數(shù)和返回值可以是任意類型,甚至是AIDL生成的接口奕翔。
你必須使用Java編程語言創(chuàng)建.aidl
文件裕寨。每一個.aidl
文件只能定義一個接口,只需要申明該接口的方法(不需要實現(xiàn))
AIDL默認支持下面幾種數(shù)據(jù)類型:
- Java編程語言中的基礎(chǔ)數(shù)據(jù)類型(int,long,char,boolean等)
- String
- CharSequence
-
List
在List 中的所有元素必須是上面支持的類型或者是其他由AIDL生成的接口派继,或者你申明的實現(xiàn)了Parcelable接口的類型宾袜。List可能被選用為泛型類,比如List<String>
.實際在接受服務(wù)一側(cè)生成的類為永遠是 ArrayList驾窟。盡管生成的方法使用的是 List接口庆猫。 -
Map
Map中的雖有元素必須是上面類型或者是其他由AIDL生成的接口,或者你申明的實現(xiàn)了Parcelable接口的類型绅络。泛型例如Map<String,Integer>
不支持月培。實際在接受服務(wù)一側(cè)生成的類為永遠是HashMap。盡管生成的方法使用的是 Map接口恩急。
你必須要為每一個上面未列出類型添加import
申明杉畜,盡管他們是作為接口定義在同一個包里面。
當(dāng)你定義的服務(wù)接口衷恭,要意識到:
- 方法可以帶有0個或者多個參數(shù)此叠,帶有返回值或者沒有返回值
- 所有的非基礎(chǔ)數(shù)據(jù)類型參數(shù)需要一個額外的用于標明參數(shù)去向的標記∝揖#可以是in拌蜘、out或者inout(參見下面的例子)⊙览觯基礎(chǔ)數(shù)據(jù)類型默認是in.而不能選擇其他方式简卧。
注意:你需要限制確實需要的方向,因為組裝參數(shù)的開銷很大烤芦。
- 所有包含在
.aidl
文件中的代碼注釋也就會包生成的 IBinder 接口中举娩,除了導(dǎo)包語句之前的聲明的注釋。 - 在AIDL中僅支持申明方法,不能申明靜態(tài)域铜涉。
下面是一個.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);
}
只需要將.aidl
文件保存到你項目的src/
目錄下面當(dāng)構(gòu)建項目的時候SDK工具會生成在項目的gen/
目錄下IBinder接口文件智玻,生成的文件名與.aidl
文件名相匹配,但是是以.java
擴展名結(jié)尾(例如IRemoteService.aidl
會生成IRemoteService.java
)
如果你使用的是Android Studio,增量構(gòu)建幾乎會立即生成Binder類芙代。如果你沒有使用Android Studio吊奢,Gradle 工具會在你下次構(gòu)建應(yīng)用時生成的binder類。一旦你寫完了.aidl
文件纹烹,你應(yīng)該用gradle assembleDebug
或者gradle assembleRelease
構(gòu)建項目页滚,這樣才能讓你的代碼鏈接到生成的類上。
2铺呵、實現(xiàn)該接口
當(dāng)你構(gòu)建你的應(yīng)用的時候裹驰,Android SDK工具生成一個根據(jù).aidl
文件生成.java
接口,生成的接口包含一個叫做的Stub
子類片挂,這個類是父接口(比如YourInterface.Stub
)的抽象實現(xiàn)幻林,并且申明了所有來自.aidl
文件的方法。
注意:
Stub
也定義了一些幫助方法音念,比較常用的是asInterface()
,這個方法傳入一個 IBinder (這個參數(shù)通常傳入到客戶端的的onServiceConnected()
回調(diào)方法中)并且返回一個Stub
的實例沪饺。參見 調(diào)用跨進程方法獲取如何轉(zhuǎn)換的更多信息。
為了實現(xiàn)由.aidl
生成的接口症昏,繼承生成的Binder接口随闽,例如YourInterface.Stub
,然后實現(xiàn)繼承自.aidl
文件中的方法肝谭。
下面是一個調(diào)用名為IRemoteService
接口的實現(xiàn)掘宪。(在上面的例子 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
類的實例攘烛,它定義了該服務(wù)的跨進程通信接口魏滚,在下一步,這個實例會暴露給客戶端這樣客戶端便能與服務(wù)端交互坟漱。
下面是在實現(xiàn)AIDL接口是應(yīng)當(dāng)注意的規(guī)則:
傳入的調(diào)用不一定保證會在主線程中執(zhí)行鼠次,因此你需要考慮多線程從開始到構(gòu)建服務(wù)保證線程安全
默認情況下,遠程過程調(diào)用(Remote Procedure Call芋齿,縮寫為 RPC)是同步的腥寇,如果知道服務(wù)會花費超過數(shù)秒來完成一個請求,你不應(yīng)當(dāng)在activity 的主線中調(diào)用該方法觅捆,因為這可能會導(dǎo)致應(yīng)用掛起(Android應(yīng)用可能會顯示應(yīng)用無法響應(yīng)的對話框)赦役,通常你應(yīng)該在客戶端的一個單獨的線程中調(diào)用該方法。
不會有異痴こ矗回傳給調(diào)用者
3掂摔、暴露接口給客戶端
一旦你實現(xiàn)了服務(wù)接口术羔,你需要將它暴露給客戶端這樣客戶端就能綁定服務(wù)。為了暴露接口給服務(wù)乙漓,繼承服務(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()](https://developer.android.com/reference/android/content/Context.html#bindService(android.content.Intent, android.content.ServiceConnection, int))鏈接到該服務(wù),客戶端的 [onServiceConnected()](https://developer.android.com/reference/android/content/ServiceConnection.html#onServiceConnected(android.content.ComponentName, android.os.IBinder)) 回調(diào)方法接受一個service的 onBind()方法返回的mBinder
實例叭披。
客戶端必須訪問接口類寥殖,如果客戶端和服務(wù)端處在不同的應(yīng)用中,客戶端必須具有一個應(yīng)用必須具有一份.aidl
的拷貝并將其存放于src/
目錄下面涩蜘。(Android SDK)會生成android.os.Binder
接口扛禽。提供客戶端訪問AIDL的方法。
當(dāng)客戶端接收到 [onServiceConnected()](https://developer.android.com/reference/android/content/ServiceConnection.html#onServiceConnected(android.content.ComponentName, android.os.IBinder)) 回調(diào)方法中 IBinder .它必須調(diào)用YourServiceInterface.Stub.asInterface(service)
將其轉(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
類.
通過跨進程通信傳遞對象
如果你有一個想通過跨進程通信接口從一個進程向另一個進程發(fā)送數(shù)據(jù)的類,你可以這樣做豆巨。但是你必須確定你的類中的代碼對跨進程通信通道另一端的是可用的剩辟,你必須支持 Parcelable 接口。支持 Parcelable 接口非常重要往扔,因為它允許Android將對象拆解成可以跨進程組裝的原語(primitives ).
要想創(chuàng)建一個支持 Parcelable協(xié)議的類贩猎,你可以采用下面的方式:
- 創(chuàng)建一個類并實現(xiàn) Parcelable接口.
- 實現(xiàn) [writeToParcel](https://developer.android.com/reference/android/os/Parcelable.html#writeToParcel(android.os.Parcel, int))方法,這個方法將當(dāng)前對象的狀態(tài)寫入到 Parcel中萍膛。
- 添加一個名為
CREATOR
的靜態(tài)域到你的類里面吭服,這個域?qū)崿F(xiàn)了 Parcelable.Creator接口。 - 最后蝗罗,創(chuàng)建一個
.aidl
文件艇棕,申明你的parcelable類(如下面的Rect.aidl
文件所示)。
如果你正在使用一個自定義構(gòu)建進程串塑,不需要添加.aidl
文件到你的構(gòu)建.跟C語言的頭文件很相似沼琉,.aidl
文件不會編譯。
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é)議的:
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 = 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();
}
}
對Rect
類的組裝非常簡單,查看 Parcel 上的其他方法可以知道你能向Parcel中寫入其他類型的值傻昙。
警告:不要忘記接受來自其他進程數(shù)據(jù)的安全性闺骚。在該例子中,
Rect
讀取來自 Parcel的四個數(shù)字妆档。但是這取決于你確保這些值是在一個可接受的范圍類僻爽,無論客戶端嘗試做什么。參考 安全和權(quán)限了解更多有關(guān)如何保證你的應(yīng)用免于惡意軟件破壞的信息过吻。
調(diào)用一個跨進程通信方法
下面是一個類想調(diào)用由AIDL定義的遠程接口所必須經(jīng)歷的步驟:
- 將
.aidl
文件放入項目的src/
目錄下面进泼。 - 申明IBinder接口實例(該示例基于AIDL生成)
- 實現(xiàn) ServiceConnection.
- 調(diào)用 [Context.bindService()](https://developer.android.com/reference/android/content/Context.html#bindService(android.content.Intent, android.content.ServiceConnection, int))蔗衡,傳入你 ServiceConnection 的實現(xiàn)。
- 在 [onServiceConnected()](https://developer.android.com/reference/android/content/ServiceConnection.html#onServiceConnected(android.content.ComponentName, android.os.IBinder))的實現(xiàn)中,你會收到一個IBinder的實例(也叫
service
)乳绕,調(diào)用YourInterfaceName.Stub.asInterface((IBinder)service)
將 [onServiceConnected()](https://developer.android.com/reference/android/content/ServiceConnection.html#onServiceConnected(android.content.ComponentName, android.os.IBinder))回調(diào)方法中的IBinder參數(shù)轉(zhuǎn)換成YourInterface
類型绞惦。 - 調(diào)用你在接口中定義的方法。你始終要要捕獲 DeadObjectException 異常洋措,這個異常會在鏈接斷開時拋出济蝉。這個異常只會有遠程方法拋出.
- 如果想斷開鏈接,用你接口的實例調(diào)用 Context.unbindService()
關(guān)于調(diào)用跨進程服務(wù)的一點說明:
- 對象跨進程采取引用計數(shù)方式
- 可以發(fā)送匿名對象作為方法的參數(shù)
想了解更多有關(guān)綁定服務(wù)的內(nèi)容菠发,參考綁定服務(wù)相關(guān)文檔王滤。
下面是摘自ApiDemos項目中遠程服務(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);
}
}
};
}