IBinder 與 Binder
一個在同進程的對象的抽象是 Object,但這個對象是不能被跨進程使用的,要想跨進程使用,在 Android 中就必須依附于 Binder Framework?;诔橄笤O計的原理,Android系統(tǒng)將一個可遠程操作的應用定義為 IBinder,在這個接口中定義了一個可遠程調(diào)用對象應該具有的屬性和方法,在代碼中實際使用的Binder 也都是繼承自 IBinder 對象。我們接下來分析 IBinder 中定義了那些接口,是怎么抽象出可遠程調(diào)用相關的方法的。
public interface IBinder {
// 查看 binder 對應的進程是否存活
public boolean pingBinder ();
// 查看 binder 是否存活,需要注意的是,可能在返回的過程中,binder 不可用
public boolean isBinderAlive ();
/**
* 執(zhí)行一個對象的方法,
*
* @param 需要執(zhí)行的命令
* @param 傳輸?shù)拿顢?shù)據(jù),這里一定不能為空
* @param 目標 Binder 返回的結(jié)果,可能為空
* @param 操作方式,0 等待 RPC 返回結(jié)果,1 單向的命令,最常見的就是 Intent.
*/
public boolean transact (int code, Parcel data, Parcel reply, int flags) throws RemoteException;
// 注冊對Binder死亡通知的觀察者,在其死亡后,會收到相應的通知
// 這里先跳過,后續(xù)
public void linkToDeath (DeathRecipient recipient, int flags) throws RemoteException;
}
如注釋中所述,最重要的方法是 transact 方法,要理解這個方法是干嘛的,得看ipcthreadstate.cpp
源代碼里的說明。
status_t IPCThreadState::transact(int32_t handle,
uint32_t code,const Parcel&data,
Parcel*reply,uint32_t flags)
{
status_t err=data.errorCheck();
flags|=TF_ACCEPT_FDS;
......
if(err==NO_ERROR){
LOG_ONEWAY(">>>> SEND from pid %d uid %d %s",getpid(),getuid(),
(flags&TF_ONE_WAY)==0?"READ REPLY":"ONE WAY");
err=writeTransactionData(BC_TRANSACTION,flags,handle,code,data,NULL);
}
if(err!=NO_ERROR){
if(reply)reply->setError(err);
return(mLastError=err);
}
if((flags&TF_ONE_WAY)==0){
......
if(reply){
err=waitForResponse(reply);
}else{
Parcel fakeReply;
err=waitForResponse(&fakeReply);
}
......
}else{
err=waitForResponse(NULL,NULL);
}
return err;
}
這是截取過后的源碼,刨除了一些支線邏輯,從這個代碼里面可以看到的主要邏輯就是:根據(jù)Uid 和 Pid (用戶id和進程id)進行相應的校驗,校驗通過后,將相應的數(shù)據(jù)寫入 writeTransactionData ,其后在 waitForResponse 里面讀取前面寫入的值,并執(zhí)行相應的方法,最后返回結(jié)果。
總結(jié)地來說,就是 IBinder 抽象了遠程調(diào)用的接口,任何一個可遠程調(diào)用的對象都應該實現(xiàn)這個接口。由于 IBinder 對象是一個高度抽象的結(jié)構(gòu),直接使用這個接口對于應用層的開發(fā)者而言學習成本太高,需要涉及到不少本地實現(xiàn),因而 Android 實現(xiàn)了 Binder 作為 IBinder 的抽象類,提供了一些默認的本地實現(xiàn),當開發(fā)者需要自定義實現(xiàn)的時候,只需要重寫 Binder 中的protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) 方法即可。
自動售貨機的故事
先偏一個題,夏天一到,天氣也變得炎熱,地鐵旁邊的自動售貨機開始有更多的人關顧了。那么售貨機是怎么工作的了?通過對這個分析,可以對Binder Proxy/Stub 模式更好地理解。
和我們打交道得是售貨機,而不是背后的零售商,道理很簡單,零售商的位置是固定的,也就意味著有很大的交通成本,而和售貨機打交道就輕松很多,畢竟售貨機就在身邊。因而從客戶端的角度上看,只需要知道售貨機即可。
再從零售商的角度來看,要和為數(shù)眾多的售貨機打交道也是不容易的事情,需要大量的維護和更新成本,如果將起交由另一個中介公司,就能夠省心不少。零售商只關心這個中介公司,按時發(fā)貨,檢查營收,這就是所有它應該完成的工作。
如上圖所示,在 Binder Framework 中也采用了類似的結(jié)構(gòu),Proxy 就相當于前面提及的售貨機,Stub 相當于這里的中介公司,在這樣的設計下,客戶端只需要和 Proxy 打交道,服務端只需要和 Stub 打交道,調(diào)理清晰很多。這種模式又被稱為 Proxy / Stub 模式,這種模式也值得我們在日后的開發(fā)中借鑒。另外需要注意的是,為了開發(fā)的需要,通常 Proxy 和 Stub 實現(xiàn)了相同的接口。
在這里 Stub 是具體的遠程方法實現(xiàn),也被稱為存根,Proxy 繼承了相應的接口,但只是在這里面調(diào)用遠程方法,并返回相應的結(jié)果。
AIDL 是什么?它是如何運作的?
雖然上述的模式幫助我們將遠程方法調(diào)用與服務具體實現(xiàn)解耦開來,但是這里面還是又不少代碼需要實現(xiàn),而且遠程方法調(diào)用這一塊應該是重復代碼,那么有什么方法來幫我們簡化這個步驟嗎?
Android 的設計者在一開始也意識到這個問題,推出了 AIDL,如下圖所示
現(xiàn)在我們來分析下,Android Framework 是如何把 AIDL 運行起來的,首先來看看一個大數(shù)相乘的例子,想把這個耗時的操作,放到另一個進程上來調(diào)用,于是定義了下面的 IMultiply.aidl 文件。
// IMultiply.aidl
package com.wandoujia.baymax.aidl;
interface IMultiply {
long multiply(long left, long right);
}
在 Android Studio 中點擊 Clean / Rebuild Project 之后,可以在 build / genreated / Source 的目錄里面找到 IMultiply.java 文件,具體的代碼如下:
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: /Users/QisenTang/Program/Wandoulabs/baymax/packages/baymax/src/main/aidl/com/wandoujia/baymax/aidl/IMultiply.aidl
*/
package com.wandoujia.baymax.aidl;
// Declare any non-default types here with import statements
public interface IMultiply extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.wandoujia.baymax.aidl.IMultiply {
private static final java.lang.String DESCRIPTOR = "com.wandoujia.baymax.aidl.IMultiply";
/**
* Construct the stub at attach it to the interface.
*/
public Stub () {
this.attachInterface (this,
DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.wandoujia.baymax.aidl.IMultiply interface,
* generating a proxy if needed.
*/
public static com.wandoujia.baymax.aidl.IMultiply asInterface (android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface (DESCRIPTOR);
if (((iin != null) && (iin instanceof com.wandoujia.baymax.aidl.IMultiply))) {
return ((com.wandoujia.baymax.aidl.IMultiply) iin);
}
return new com.wandoujia.baymax.aidl.IMultiply.Stub.Proxy (obj);
}
@Override
public android.os.IBinder asBinder () {
return this;
}
@Override
public boolean onTransact (int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString (DESCRIPTOR);
return true;
}
case TRANSACTION_multiply: {
data.enforceInterface (DESCRIPTOR);
long _arg0;
_arg0 = data.readLong ();
long _arg1;
_arg1 = data.readLong ();
long _result = this.multiply (_arg0,
_arg1);
reply.writeNoException ();
reply.writeLong (_result);
return true;
}
}
return super.onTransact (code,
data,
reply,
flags);
}
private static class Proxy implements com.wandoujia.baymax.aidl.IMultiply {
private android.os.IBinder mRemote;
Proxy (android.os.IBinder remote) {
mRemote = remote;
}
@Override
public android.os.IBinder asBinder () {
return mRemote;
}
public java.lang.String getInterfaceDescriptor () {
return DESCRIPTOR;
}
@Override
public long multiply (long left, long right) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain ();
android.os.Parcel _reply = android.os.Parcel.obtain ();
long _result;
try {
_data.writeInterfaceToken (DESCRIPTOR);
_data.writeLong (left);
_data.writeLong (right);
mRemote.transact (Stub.TRANSACTION_multiply,
_data,
_reply,
0);
_reply.readException ();
_result = _reply.readLong ();
} finally {
_reply.recycle ();
_data.recycle ();
}
return _result;
}
}
static final int TRANSACTION_multiply = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public long multiply (long left, long right) throws android.os.RemoteException;
}
這段自動的生成的代碼,可讀性上不是很好,但不影響我們進行分析。這段自動生成的代碼中,最重要的是三個部分,Proxy,Stub和asInterface。
首先看看Proxy的實現(xiàn),首先是將遠程的Binder作為參數(shù)傳入進來,再來看看 multiply 這個方法里面的下面幾個步驟。首先是將left 和 right的參數(shù)寫入到_data中去,同時在 遠程binder 調(diào)用結(jié)束后,得到返回的 _reply ,在沒有異常的情況下,返回_reply.readLong()的結(jié)果。根據(jù) Android Binder 完全解析(一)概述 提到的內(nèi)容,這里可以粗略地將 Binder 看做遠程服務拋出的句柄,通過這個句柄就可以執(zhí)行相應的方法了。這里需要額外說明的是,寫入和傳輸?shù)臄?shù)據(jù),都是 Parcelable,Android Framework 中提供的。
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeLong(left);
_data.writeLong(right);
mRemote.transact(Stub.TRANSACTION_multiply, _data, _reply, 0);
_reply.readException();
_result = _reply.readLong();
再來看看Stub里面的實現(xiàn),Stub本身繼承了Binder抽象類,本身將作為一個句柄,實現(xiàn)在服務端,但傳遞給客戶端使用。同樣也看看 onTransact里面的方法,首先是通過 long _arg0; _arg0 = data.readLong();讀取 left 和 right 參數(shù),在this.multiply(_arg0, _arg1)計算過后,將結(jié)果寫入到reply中。
細心的讀者,可以從上面的描述中,發(fā)現(xiàn)一些有意思的東西。Proxy 是寫入?yún)?shù),讀取值;Stub 是讀取參數(shù),寫入值。正好是一對,那因此我們是不是可以做出這樣的論斷呢?Proxy 和 Stub 操作的是一份數(shù)據(jù)?恭喜你,答對了。
在前文提及的內(nèi)容里,用戶空間和內(nèi)核空間是互相隔離的,客戶端和服務端在同一用戶空間,而 Binder Driver 在內(nèi)核空間中,常見的方式是通過copy_from_user 和 copy_to_user 兩個系統(tǒng)調(diào)用來完成,但 Android Framework 考慮到這種方式涉及到兩次內(nèi)存拷貝,在嵌入式系統(tǒng)中不是很合適,于是 Binder Framework 通過 ioctl 系統(tǒng)調(diào)用,直接在內(nèi)核態(tài)進行了相關的操作,節(jié)省了寶貴的空間。
可能大家也注意點 Proxy這里是private權(quán)限的,外部是無法訪問的,但這里是 Android 有意為之,拋出了 asInterface方法,這樣避免了對 Proxy可能的修改。
系統(tǒng)使用AIDL的場景
AIDL 被Android 系統(tǒng)廣泛使用,在許多地方都能看到相應的場景,這里以 電話服務 作為例子,來簡單說明下如何在系統(tǒng)沒有提供掛斷電話的API的情況下強行掛電話。
通過的 ITelephony.aidl 的查看,可以在里面找到相應的接口,不過這個類是 internal 包里面的,應用層無法訪問。那怎么來完成這個操作了?
/**
* End call if there is a call in progress, otherwise does nothing.
*
* @return whether it hung up
*/
boolean endCall();
首先在相同包名下申明同樣的AIDL文件,再通過編譯后,就會生成相應的 Stub 文件。其次通過反射拿到 TELEPHONY_SERVICE 的binder。這個 binder 就是可以操作另一個進程來掛斷電話的句柄。將這個binder 作為 Proxy 的參數(shù),并通過 asInterface 注入進去,從何獲得相應的接口,最后調(diào)用 telephony.endCall() 即可完成操作。
Method method = Class.forName ("android.os.ServiceManager")
.getMethod ("getService",
String.class);
IBinder binder = (IBinder) method.invoke (null,
new Object[]{TELEPHONY_SERVICE});
ITelephony telephony = ITelephony.Stub.asInterface (binder);
telephony.endCall ();