原文位址:AIDL
- 一、前言
- 二、定義AIDL接口
- 2.1 建立.aidl檔案
- 2.2 實作接口
- 2.3 向調用方暴露接口
- 三、通過程序間通信傳遞對象
- 四、調用IPC方法
一、前言
ALDI(Android interface definition language)跟其他接口定義語言類似。你可以用它定義調用方與service之間的程式接口以完成程序間通信。在Android環境中,通常一個程序不能通路另一個程序的記憶體,這時就需要某種方式将對象分解為作業系統可以了解的原始資料,并由系統将這個對象傳遞給另一個程序,這種方式,就是AIDL。
在定義AIDL以前,你需要知道:對AIDL接口的調用不是直接的方法調用。而且被調用的方法處于的線程不一定就是調用方的線程:決定它們兩份是否處于同一線程的是它們是否在同一個程序。下面分三種情況進行叙述:
- 如果是本程序内的調用,那麼被調用方會在同一個線程執行。隻要不是跨程序調用,那麼不管你是在UI線程還是其他線程調用AIDL方法,AIDL接口都會在調用方所在的線程執行。從這個角度看,如果你隻在本地線程通路bound service,那麼你可以完全控制此AIDL接口被哪個線程執行。但是,如果不是跨程序調用,最好不要用AIDL方式,直接實作Binder類即可,更簡單友善
- 從remote程序的調用會被分發到由平台在你的程序維護的一個線程池中。你的service必須準備好從未知線程中接收請求,而且可能是同時進入的多個請求。換句話說,你的AIDL接口實作應該是線程安全的
- 關鍵字
可以修改遠端調用的行為。如果你指定了此關鍵字,那麼遠端調用将不會阻塞:遠端調用會發送資料然後立刻傳回。而你的AIDL接口實作會從Binder線程池中收到格式化的調用資料。如果你在本地調用中指定了此關鍵字,那麼此關鍵字将不會起作用,你的調用仍然是同步的oneway
二、定義AIDL接口
AIDL接口使用java文法編寫,儲存為.aidl檔案,放置在bound service所在應用和要綁定這個service的應用的源碼路徑下(src/路徑下)。
如果你的應用包含.aidl檔案,那麼當build時,Android SDK工具會基于你的.aidl檔案生成IBinder接口并儲存在項目的gen/目錄。你的service必須實作這個IBinder接口,然後調用方就可以通過這個IBinder接口與你的service做跨程序通信了。
要通過AIDL建立bound service,需要以下步驟:
-
建立.aidl檔案
這個檔案定義了接口的函數原型
-
實作這個接口
Android SDK工具會根據你的.aidl檔案生成一個java接口。這個接口有一個Stub内部抽象類,其繼承了Binder,并實作了你的AIDL接口。你必須繼承Stub類并實作相關方法
-
向調用方暴露接口
實作bound service,并通過onBind()方法傳回Stub類的對象
注意:在AIDL接口釋出以後,你對它做的任何修改,都需要相容以前的版本,以避免使用你的service的應用崩潰
2.1 建立.aidl檔案
你可以在AIDL檔案中聲明接口、一個或多個方法、方法的參數和傳回值。其中,方法的參數和傳回值可以是任何類型,甚至是其他AIDL接口。
.aidl檔案使用的文法是java文法,其内容就是一個java接口。
預設情況下,AIDL支援以下資料類型:
- java基本類型,如int,long,char,boolean等
- String
- CharSequence
-
List
List中的所有元素都必須是目前聲明的資料類型的一部分、或者其他AIDL接口、或者你聲明的parcelable對象。List支援泛型。在調用方收到的總是ArrayList(雖然service聲明的是List)
-
Map
Map中的所有元素都必須是目前聲明的資料類型的一部分、或者其他AIDL接口、或者你聲明的parcelable對象。Map不支援泛型。在調用方收到的總是HashMap(雖然service聲明的是Map)
對于上面沒有提到的其他類型,你需要通過import在AIDL檔案中聲明,即使它們在同一個包内。
在定義你的service接口前,你需要知道:
- 方法可以沒有參數,也可以傳回空
-
所有非基本類型參數需要聲明方向标記,可以是in,out或者inout。基本類型預設為in,但是可以聲明為其他
注意:你應該根據實際需要設定這些方向标記,因為序列化這些參數非常消耗資源
- .aidi檔案中的代碼注釋會移植到IBinder接口中(除import和包名之前的注釋)
下面是一個.aidl檔案的示例:
// IRemoteService.aidl
package com.example.android;
// 非預設類型資料需要在這裡通過import導入,即使它們在同一包中
/** service interface 示例 */
interface IRemoteService {
/** 擷取service的process ID */
int getPid();
/** 此方法隻是為了說明你可以使用的基本資料類型,沒有特殊含義
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
将.aidl檔案儲存在src/目錄,當build時,Android SDK工具會根據它生成IBinder接口,并儲存在gen/目錄。生成的檔案名跟.aidl檔案名相同,隻是字尾換為.java
2.2 實作接口
當build應用時,Android SDK工具會根據.aidl生成.java的接口。生成的接口包含一個Stub,其是一個抽象類,實作了父類接口,并聲明了所有.aidl檔案的方法。
注意:Stub還定義了一些有用的方法,如asInterface(),此方法接受一個IBinder(通常是傳遞到onServiceConnected()方法的那個IBinder),傳回一個stub接口的執行個體
你需要繼承生成的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
}
};
在實作AIDL接口時,你需要知道以下知識:
- 對接口方法的調用不一定在主線程,你需要考慮多線程情況,并使你的service線程安全
- 預設情況下,遠端調用是同步的。如果你知道service需要一定時間才能完成請求,那麼你不應該在activity的主線程調用AIDL方法
- 你抛出的異常不會被發送給調用方
2.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()綁定service時,調用方的onServiceConnected()方法會收到service的onBind()方法傳回的mBinder執行個體。
由于調用方與service在不同的應用中,是以調用方要通路mBinder中的接口,還需要将src/路徑的.aidl檔案複制一份。
當調用方收到IBinder對象時,必須調用YourServiceInterface.Stub.asInterface(service)方法來将IBinder強轉為YourServiceInterface。示例如下:
IRemoteService mIRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
// 當與service的連接配接建立成功時會調用此方法
public void onServiceConnected(ComponentName className, IBinder service) {
// 擷取IRemoteInterface執行個體,你可以通過它調用service的方法
mIRemoteService = IRemoteService.Stub.asInterface(service);
}
// 當與service的連接配接意外中斷時調用
public void onServiceDisconnected(ComponentName className) {
Log.e(TAG, "Service has unexpectedly disconnected");
mIRemoteService = null;
}
};
三、通過程序間通信傳遞對象
通過程序間通信,你可以在程序間傳遞對象。但是,你首先得確定你的類對于IPC通道的另一端是可見的,并且你的類需要實作Parcelable接口。實作Parcelable接口意味着允許Android系統将你的對象分解為原始資料以傳遞到另一個程序。
要建立支援Parcelable協定的類,你需要以下幾步:
- 實作Parcelable接口
- 實作writeToParcel方法,此方法會将對象的目前狀态寫入到Parcel中
- 添加靜态域CREATOR,其是實作了Parcelable.Creator接口的類的執行個體
-
建立.aidl檔案并聲明你的可序列化類
如果你使用自己定制的build過程,那麼不要将.aidl檔案加入到你的build過程中。.aidl類似于C語言的頭檔案,是不會被編譯的
系統會根據你在AIDL中聲明的方法和域,序列化/反序列化你的對象。
下面是一個Rect.aidl檔案的示例,其聲明了一個可序列化的Rect類:
package android.graphics;
// 聲明Rect類,這樣AIDL才能找到它,并知道它已經支援了可序列化協定
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();
}
}
四、調用IPC方法
調用方要調用AIDL中的遠端接口,需要實作以下步驟:
- 将.aidl檔案放置在src/目錄
- 聲明一個IBinder接口的執行個體(根據AIDL生成)
- 實作ServiceConnection
- 調用Context.bindService()并傳入ServiceConnection
- 在你的onServiceConnected()方法中,你會收到IBinder執行個體,調用YourInterfaceName.Stub.asInterface((IBinder)service)将傳回的IBinder強轉為YourInterface類型
- 調用YourInterface執行個體中的方法。當連接配接斷開時,這些方法會抛出DeadObjectException異常,你可以捕獲它們。DeadObjectException異常是遠端方法會抛出的唯一異常
- 通信完成後,使用Context.unbindService()方法解綁
調用IPC service的一些說明:
- 對象的引用是跨程序的(Objects are reference counted across processes)
- 你可以使用匿名對象作為參數
下面是調用基于AIDL的bound service的示例:
public static class Binding extends Activity {
/** 我們将要調用的service的接口 */
IRemoteService mService = null;
/** service的另一個接口 */
ISecondary mSecondaryService = null;
Button mKillButton;
TextView mCallbackText;
private boolean mIsBound;
/**
* activity标準初始化過程:設定UI然後等待使用者與UI互動
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.remote_service_binding);
//監聽button點選事件
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.");
}
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder service) {
mService = IRemoteService.Stub.asInterface(service);
mKillButton.setEnabled(true);
mCallbackText.setText("Attached.");
try {
mService.registerCallback(mCallback);
} catch (RemoteException e) {
// 在這裡,service在我們操作以前就崩潰了,我們很快會收到連接配接
// 斷開回調,在那裡我們可以嘗試重連
}
Toast.makeText(Binding.this, R.string.remote_service_connected,
Toast.LENGTH_SHORT).show();
}
public void onServiceDisconnected(ComponentName className) {
mService = null;
mKillButton.setEnabled(false);
mCallbackText.setText("Disconnected.");
Toast.makeText(Binding.this, R.string.remote_service_disconnected,
Toast.LENGTH_SHORT).show();
}
};
private ServiceConnection mSecondaryConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder service) {
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) {
// 通過接口名稱與service建立連接配接。這樣的話其他應用可以通過使用
// 相同的接口名稱來替換遠端service
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 (mService != null) {
try {
mService.unregisterCallback(mCallback);
} catch (RemoteException e) {
}
}
// 斷開連接配接
unbindService(mConnection);
unbindService(mSecondaryConnection);
mKillButton.setEnabled(false);
mIsBound = false;
mCallbackText.setText("Unbinding.");
}
}
};
private OnClickListener mKillListener = new OnClickListener() {
public void onClick(View v) {
// 要kill掉service所在的程序,需要知道此程序的id
if (mSecondaryService != null) {
try {
int pid = mSecondaryService.getPid();
// 注意,雖然你可以使用下面的API根據PID申請kill任何程序,
// 但是kernel會限制你實際上可以kill哪些程序。隻有你的應用
// 所在的程序、你的應用建立的附加程序、與你的應用有相同UID
// 的程序可以被你kill
Process.killProcess(pid);
mCallbackText.setText("Killed service process.");
} catch (RemoteException ex) {
Toast.makeText(Binding.this,
R.string.remote_call_failed,
Toast.LENGTH_SHORT).show();
}
}
}
};
// ----------------------------------------------------------------------
// 處理回調的相關代碼
// ----------------------------------------------------------------------
/**
* 這個接口用于擷取遠端service的回調
*/
private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
/**
* 當遠端service要通知你資料有變化時會調用此方法。注意,跨程序的調用
* 請求會被發送到每個程序的線程池中,是以下面的代碼不是在主線程運作的,
* 是以,你需要使用handler更新UI
*/
public void valueChanged(int value) {
mHandler.sendMessage(mHandler.obtainMessage(BUMP_MSG, value, ));
}
};
private static final int BUMP_MSG = ;
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);
}
}
};
}