Android官方文件—APP元件(Services)(AIDL)
Android介面定義語言(AIDL)
AIDL(Android介面定義語言)類似於您可能使用過的其他IDL。它允許您定義客戶端和服務商定的程式設計介面,以便使用程序間通訊(IPC)相互通訊。在Android上,一個程序無法正常訪問另一個程序的記憶體。所以說說,他們需要將物件分解為作業系統可以理解的基元,併為您跨越該邊界編組物件。執行編組的程式碼編寫起來很繁瑣,因此Android使用AIDL為您處理它。
注意:僅當您允許來自不同應用程式的客戶端訪問IPC服務並希望在您的服務中處理多執行緒時,才需要使用AIDL。如果您不需要跨不同的應用程式執行併發IPC,則應通過實現Binder來建立介面,或者,如果要執行IPC,但不需要處理多執行緒,請使用Messenger實現介面。無論如何,在實施AIDL之前,請確保您瞭解繫結服務。
在開始設計AIDL介面之前,請注意對AIDL介面的呼叫是直接函式呼叫。您不應該對發生呼叫的執行緒做出假設。根據呼叫是來自本地程序中的執行緒還是遠端程序,會發生什麼情況會有所不同。特別:
- 從本地程序進行的呼叫在進行呼叫的同一執行緒中執行。如果這是您的主UI執行緒,則該執行緒繼續在AIDL介面中執行。如果它是另一個執行緒,那就是在服務中執行程式碼的執行緒。因此,如果只有本地執行緒正在訪問該服務,您可以完全控制在其中執行哪些執行緒(但如果是這種情況,那麼您根本不應該使用AIDL,而應該通過實現Binder來建立介面) )。
- 來自遠端程序的呼叫將從平臺在您自己的程序內維護的執行緒池中排程。您必須為來自未知執行緒的傳入呼叫做好準備,同時發生多個呼叫。換句話說,AIDL介面的實現必須完全是執行緒安全的。
- oneway關鍵字修改遠端呼叫的行為。使用時,遠端呼叫不會阻止;它只是傳送交易資料並立即返回。介面的實現最終將此作為來自Binder執行緒池的常規呼叫作為普通遠端呼叫接收。如果單向使用本地呼叫,則沒有影響,呼叫仍然是同步的。
定義AIDL介面
您必須使用Java程式語言語法在.aidl檔案中定義AIDL介面,然後將其儲存在託管服務的應用程式和繫結到服務的任何其他應用程式的原始碼(在src /目錄中)中。
當您構建包含.aidl檔案的每個應用程式時,Android SDK工具會生成基於.aidl檔案的IBinder介面,並將其儲存在專案的gen /目錄中。該服務必須適當地實現IBinder介面。然後,客戶端應用程式可以繫結到服務並從IBinder呼叫方法來執行IPC。
要使用AIDL建立服務,請按照下列步驟操作:
1.建立.aidl檔案
此檔案使用方法簽名定義程式設計介面。
2.實現介面
Android SDK工具基於.aidl檔案生成Java程式語言的介面。此介面有一個名為Stub的內部抽象類,它擴充套件了Binder並實現了AIDL介面的方法。您必須擴充套件Stub類並實現這些方法。
3.將介面公開給客戶端
實現一個Service並重寫onBind()以返回Stub類的實現。
警告:首次釋出後對AIDL介面所做的任何更改都必須保持向後相容,以避免破壞使用您服務的其他應用程式。也就是說,因為必須將.aidl檔案複製到其他應用程式才能訪問服務的介面,所以必須保持對原始介面的支援。
1.建立.aidl檔案
AIDL使用一種簡單的語法,允許您使用一個或多個可以獲取引數和返回值的方法宣告介面。引數和返回值可以是任何型別,甚至是其他AIDL生成的介面。
您必須使用Java程式語言構造.aidl檔案。每個.aidl檔案必須定義單個介面,並且只需要介面宣告和方法簽名。
預設情況下,AIDL支援以下資料型別:
- Java程式語言中的所有原始型別(例如int,long,char,boolean等)
List
List中的所有元素必須是此列表中支援的資料型別之一,或者是您宣告的其他AIDL生成的介面或parcelables之一。列表可以可選地用作“通用”類(例如,List<String>)。另一方接收的實際具體類始終是ArrayList,儘管生成該方法以使用List介面。
Map中的所有元素必須是此列表中支援的資料型別之一,或者是您宣告的其他AIDL生成的介面或parcelables之一。不支援通用對映(例如Map<String,Integer>形式的對映)。另一方接收的實際具體類始終是HashMap,儘管生成的方法是使用Map介面。
您必須為上面未列出的每個其他型別包含import語句,即使它們與您的介面在同一個包中定義。
定義服務介面時,請注意:
- 方法可以接受零個或多個引數,並返回值或void。
- 所有非原始引數都需要一個方向標記來指示資料的傳輸方式。 in,out或inout(參見下面的示例)。
原始型別預設為in。
警告:您應該將方向限制為真正需要的方向,因為編組引數很昂貴。
- .aidl檔案中包含的所有程式碼註釋都包含在生成的IBinder介面中(import和package語句之前的註釋除外)。
- 僅支援方法;您無法在AIDL中公開靜態欄位。
這是一個示例.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 /目錄中,當您構建應用程式時,SDK工具會在專案的gen /目錄中生成IBinder介面檔案。生成的檔名與.aidl檔名匹配,但副檔名為.java(例如,IRemoteService.aidl會生成IRemoteService.java)。
如果您使用Android Studio,則增量構建幾乎會立即生成binder類。如果您不使用Android Studio,那麼Gradle工具會在您下次構建應用程式時生成binder類 - 您應該在編寫.aidl檔案後立即使用gradle assembleDebug(或gradle assembleRelease)構建專案,這樣您的程式碼可以連結到生成的類。
2.實現介面
構建應用程式時,Android SDK工具會生成一個以.aidl檔案命名的.java介面檔案。生成的介面包括一個名為Stub的子類,它是其父介面的抽象實現(例如,YourInterface.Stub),並宣告.aidl檔案中的所有方法。
注意:Stub還定義了一些輔助方法,最明顯的是asInterface(),它接受IBinder(通常是傳遞給客戶端的onServiceConnected()回撥方法的方法)並返回存根介面的例項。有關的更多詳細資訊,請參閱呼叫IPC方法一節。
要實現從.aidl生成的介面,請擴充套件生成的Binder介面(例如,YourInterface.Stub)並實現從.aidl檔案繼承的方法。
下面是一個使用匿名例項的名為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可能會顯示“應用程式沒有響應”對話方塊) - 你應該通常從客戶端的單獨執行緒中呼叫它們。
- 您丟擲的異常不會被髮送回呼叫者。
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
}
};
}
現在,當客戶端(例如活動)呼叫bindService()連線到此服務時,客戶端的onServiceConnected()回撥接收服務的onBind()方法返回的mBinder例項。
客戶端還必須能夠訪問介面類,因此如果客戶端和服務位於不同的應用程式中,則客戶端的應用程式必須在其src /目錄中生成.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類。
通過IPC傳遞物件
如果您有一個類要通過IPC介面從一個程序傳送到另一個程序,則可以執行此操作。但是,您必須確保您的類的程式碼可用於IPC通道的另一端,並且您的類必須支援Parcelable介面。支援Parcelable介面非常重要,因為它允許Android系統將物件分解為可以跨程序編組的基元。
要建立支援Parcelable協議的類,必須執行以下操作:
- 使您的類實現Parcelable介面。
- 實現writeToParcel,它獲取物件的當前狀態並將其寫入Parcel。
- 向您的類新增一個名為CREATOR的靜態欄位,該類是實現Parcelable.Creator介面的物件。
- 最後,建立一個宣告您的parcelable類的.aidl檔案(如下面的Rect.aidl檔案所示)。 如果您使用的是自定義生成過程,請不要將.aidl檔案新增到您的構建中。與C語言中的標頭檔案類似,此.aidl檔案未編譯。
AIDL在它生成的程式碼中使用這些方法和欄位來編組和解組您的物件。
例如,這是一個Rect.aidl檔案,用於建立一個可以parcelable的Rect類:
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 = 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上的其他方法,檢視可以寫入包的其他型別的值。
警告:不要忘記從其他程序接收資料的安全隱患。在這種情況下,Rect從包中讀取四個數字,但是由您來確保這些數字在呼叫者嘗試執行的任何值的可接受範圍內。有關如何確保應用程式免受惡意軟體攻擊的詳細資訊,請參閱安全性和許可權。
呼叫IPC方法
以下是呼叫類呼叫AIDL定義的遠端介面必須採取的步驟:
- 在專案src /目錄中包含.aidl檔案。
- 宣告IBinder介面的例項(基於AIDL生成)。
- 實現ServiceConnection。
- 呼叫Context.bindService(),傳入ServiceConnection實現。
- 在onServiceConnected()的實現中,您將收到一個IBinder例項(稱為服務)。呼叫YourInterfaceName.Stub.asInterface((IBinder)服務)將返回的引數強制轉換為YourInterface型別。
- 呼叫您在介面上定義的方法。您應始終捕獲DeadObjectException異常,這些異常在連線斷開時丟擲;這將是遠端方法丟擲的唯一例外。
- 要斷開連線,請使用您的介面例項呼叫Context.unbindService()。
關於呼叫IPC服務的一些註釋:
- 物件是跨程序的引用計數。
- 您可以將匿名物件作為方法引數傳送。
有關繫結到服務的更多資訊,請閱讀“繫結服務”文件。
下面是一些示例程式碼,演示呼叫AIDL建立的服務,取自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.
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);
}
}
};
}