很早就想寫一篇Binder的文章了火的,但是遲遲沒寫出來州袒,因?yàn)锽inder機(jī)制牽涉到的知識點(diǎn)太多了蹬音,有Java層的Binder坦报,也有底層的binder驅(qū)動库说。通常我們在Java層面做應(yīng)用開發(fā)牽涉到Binder的,有AIDL片择、Messenger等潜的。如何寫AIDL、Messenger相關(guān)的代碼并不難字管,難點(diǎn)在于理解Binder的原理啰挪。如果想要系統(tǒng)地學(xué)習(xí)Binder,推薦看《Android開發(fā)藝術(shù)探索》的相關(guān)章節(jié)以及老羅《Android進(jìn)程間通信(IPC)機(jī)制Binder簡要介紹和學(xué)習(xí)計(jì)劃》系列嘲叔,分別對應(yīng)的是Java層的Binder機(jī)制和底層的Binder機(jī)制亡呵。還有一些其它的優(yōu)秀的文章,文后會有推薦硫戈。
本文不打算深入地介紹Binder 機(jī)制的原理锰什,畢竟這不是一兩篇文章能介紹的清楚的,而且現(xiàn)在網(wǎng)上也有很多優(yōu)秀的文章丁逝。本文想介紹的是我個人通過對Binder地學(xué)習(xí)汁胆,對Binder機(jī)制和Android系統(tǒng)的一些理解。
限于水平果港,難免有誤沦泌,還請指正。
Binder基本介紹
Binder是Android系統(tǒng)運(yùn)行的一個重要基石辛掠,做Android開發(fā)的應(yīng)該沒有沒聽說過Binder的吧谢谦,那么Binder到底是個什么東西呢?
Binder萝衩,最簡單來說就是一個Java類回挽,全路徑名是 android.os.Binder
,一般我們看到包名是 android.os
的猩谊,就應(yīng)該想到它是系統(tǒng)運(yùn)行相關(guān)的類千劈。這個Binder類實(shí)現(xiàn)了IBinder接口,代表這個類的對象具有跨進(jìn)程通訊的能力牌捷。從IPC角度上講墙牌,Binder是Android中一種跨進(jìn)程通訊方式。Binder還可以理解為一個虛擬的物理設(shè)備暗甥,這是因?yàn)樗怯序?qū)動的喜滨,但是又不像一般的硬件那樣有物理實(shí)體,它的設(shè)備驅(qū)動是/dev/binder撤防,這個驅(qū)動是Android特有的虽风,Linux中沒有。從Framework層講,是ServiceManager與各種manager辜膝,比如ActivityManagerService无牵、WindowManagerService和各種ManagerService進(jìn)行通訊的橋梁;從應(yīng)用層來說厂抖,Binder是客戶端和服務(wù)端進(jìn)行通訊的媒介茎毁。
我覺得不管從什么角度來看Binder,Binder的作用就是用來進(jìn)行跨進(jìn)程通訊 (IPC) 的验游。這里充岛,我們需要簡單介紹下IPC保檐,以便于更好的理解Binder的作用耕蝉。
IPC方式
任何一個系統(tǒng)都需要有相應(yīng)的IPC機(jī)制,Linux上面可以通過管道夜只、System V IPC垒在,即消息隊(duì)列/共享內(nèi)存/信號量,或者Socket的方式來進(jìn)行跨進(jìn)程通訊扔亥,Android是基于Linux的场躯,也就是說Android也可以使用這些IPC方式,那么Android系統(tǒng)為什么還要再引入Binder這種IPC方式呢旅挤?這個問題踢关,我覺得可以從三個方面來回答:使用方便、傳輸性能粘茄、安全签舞。
- 使用方便: Binder是基于C/S架構(gòu)的,利用了面向?qū)ο蟮乃枷肫獍辏瑢τ陂_發(fā)者使用來說儒搭,很方便。從這個角度來說芙贫,共享內(nèi)存這種方式就不適合搂鲫,因?yàn)樗褂锰珡?fù)雜。
- 傳輸性能:Android畢竟是移動設(shè)備磺平,移動設(shè)備就要考慮內(nèi)存占用和耗電量的問題魂仍,Binder只需要拷貝內(nèi)存1次,而管道拣挪、消息隊(duì)列擦酌、Socket都需要對數(shù)據(jù)拷貝2次。
- 安全:傳統(tǒng)IPC沒有任何安全措施媒吗,完全依賴上層協(xié)議來確保仑氛。傳統(tǒng)IPC訪問接入點(diǎn)是開放的,無法建立私有通道。比如命名管道的名稱锯岖,system V的鍵值介袜,socket的ip地址或文件名都是開放的,只要知道這些接入點(diǎn)的程序都可以和對端建立連接出吹,不管怎樣都無法阻止惡意程序通過猜測接收方地址獲得連接遇伞。
IPC | 數(shù)據(jù)拷貝次數(shù) |
---|---|
共享內(nèi)存 | 0 |
Binder | 1 |
Socket/管道/消息隊(duì)列 | 2 |
基于以上這些原因,Android引入了Binder這種IPC方式捶牢,基于C/S架構(gòu)鸠珠,傳輸過程只需要1次拷貝,為發(fā)送方添加UID/PID身份驗(yàn)證秋麸,既支持實(shí)名Binder也支持匿名Binder渐排,安全性高。
Binder中的4種角色
- Client:客戶端灸蟆,使用服務(wù)的一端
- Server:服務(wù)端驯耻,提供服務(wù)的一端
- ServerManager:ServiceManager的作用是將字符形式的Binder名字轉(zhuǎn)化成Client中對該Binder的引用,使得Client能夠通過Binder名字獲得對Server中Binder實(shí)體的引用炒考。其實(shí)ServiceManager端也是一個Server端可缚,也有自己的Binder實(shí)體,對于ServerManager端來說斋枢,其它端都是client端帘靡。它是一個守護(hù)進(jìn)程,用來管理Server瓤帚,并向Client提供查詢Server接口的能力描姚。
- Binder驅(qū)動:驅(qū)動負(fù)責(zé)進(jìn)程之間Binder通信的建立,Binder在進(jìn)程之間的傳遞缘滥,Binder引用計(jì)數(shù)管理轰胁,數(shù)據(jù)包在進(jìn)程之間的傳遞和交互等一系列底層支持。
Client朝扼、Server和Service Manager實(shí)現(xiàn)在用戶空間中赃阀,Binder驅(qū)動程序?qū)崿F(xiàn)在內(nèi)核空間中。 Client和Server之間的進(jìn)程間通信通過Binder驅(qū)動程序間接實(shí)現(xiàn)擎颖。Binder驅(qū)動程序和Service Manager在Android平臺中已經(jīng)實(shí)現(xiàn)榛斯,對于我們應(yīng)用開發(fā)者來說,只需要實(shí)現(xiàn)自己的Client和Server就行搂捧,常見的就是使用AIDL的方式來實(shí)現(xiàn)IPC驮俗。
應(yīng)用開發(fā)中的Binder使用
Binder的那些概念介紹是挺枯燥乏味的,在Android開發(fā)中我們無時不刻不在使用Binder允跑,那么我們就結(jié)合平時的開發(fā)來了解一些使用的Binder機(jī)制的地方王凑。
1 Activity啟動
Activity的啟動需要用到ActivityManagerService搪柑,但是我們的App進(jìn)程和ActivityManagerService所在的進(jìn)程不是同一個進(jìn)程,所以就需要用到進(jìn)程間通訊了索烹。在App進(jìn)程中我們拿到的是ActivityManagerService的一個分身工碾,也就是ActivityManagerProxy,這個ActivityManagerProxy與ActivityManagerService都實(shí)現(xiàn)了IActivityManager接口百姓,因此它們具有相同的功能渊额,但是ActivityManagerProxy只是做了一個中轉(zhuǎn),創(chuàng)建兩個Parcel對象垒拢,一個用于攜帶請求的參數(shù)旬迹,一個用于拿到請求結(jié)果,然后調(diào)用transact方法求类,通過Binder驅(qū)動奔垦,ActivityManagerService的onTransact方法會被調(diào)用,然后根據(jù)相應(yīng)的code仑嗅,調(diào)用相應(yīng)的方法宴倍,并把處理結(jié)果返回张症。
在這個過程中仓技,我們的App進(jìn)程就是Client,ActivityManagerService所在的進(jìn)程是Service俗他。
但是Activity的啟動過程還沒有完脖捻,ActivityManagerService還會調(diào)用我們App所在進(jìn)程的ApplicationThread來最終完成Activity的啟動,其實(shí)ActivityManagerService拿到的也是ApplicationThread的一個分身ApplicationThreadProxy兆衅,通過這個分身地沮,ApplicationThread相應(yīng)的方法會被調(diào)用。
在這個過程中羡亩,我們的App端是Server摩疑,ActivityManagerService所在的進(jìn)程是Client。
還有一個問題我們要注意畏铆,ActivityThread有一個內(nèi)部類H(一個Hander)雷袋,ApplicationThread方法內(nèi)部都會通過這個Handler來發(fā)送消息,最終調(diào)用到ActivityThread的方法辞居。為什么要這么做呢楷怒?
在分析源碼的過程中,很長一段時間瓦灶,這個問題都困擾著我鸠删,直到有一天對Binder的理解加深了,我才明白:Binder服務(wù)端的方法都是運(yùn)行在Binder線程池的一個線程中的贼陶,所以要通過Hander刃泡,把方法的調(diào)用切換到主線程中來巧娱。
2 Intent攜帶數(shù)據(jù)
我們都知道Intent可以傳遞的數(shù)據(jù)包含:基本類型、String烘贴、實(shí)現(xiàn)了Serializable接口或者Parcelable接口的類以及對應(yīng)的數(shù)組或者集合類家卖。其實(shí)Intent中的數(shù)據(jù)都是通過Bundle來攜帶的,那么我們就要有個疑問了庙楚,為什么限定只能是這些類型的數(shù)據(jù)上荡,而不是任意的數(shù)據(jù)類型呢?
歸根結(jié)底馒闷,限制這些類型的是Parcel這個類酪捡。如果我們查看源碼的話就會看到,Bundle其實(shí)也是用到了Parcel這個類纳账。
Parcel 逛薇,“包裹的意思”,它的作用就是為了在IPC過程中存放數(shù)據(jù)疏虫。我們要知道一點(diǎn)永罚,進(jìn)程間傳遞數(shù)據(jù),實(shí)際上就是二進(jìn)制數(shù)據(jù)卧秘,所以對于非基本類型呢袱,必然存在著序列化和反序列過程,這也是為什么要求Intent傳遞的非基本類型數(shù)據(jù)必須實(shí)現(xiàn)Serializable或者Parcelable接口的原因翅敌。
至于Parcel在IPC過程中使用到的地方羞福,我們可以看一段代碼,這個是我仿造著AIDL生成的文件蚯涮,自己手寫的一個Binder服務(wù)端治专。看一下Proxy類的add方法遭顶,實(shí)際上就是先創(chuàng)建兩個Parcel對象张峰,一個通過調(diào)用 writexxx 方法用于存放請求數(shù)據(jù),一個是通過調(diào)用 readxxx 方法獲取結(jié)果棒旗。Proxy真正干的就是這些喘批,真正計(jì)算的還是服務(wù)端Stub的實(shí)現(xiàn)類。當(dāng)Proxy調(diào)用 mRemote.transact(TRANSACTION_add, _data, _reply, 0);
方法后嗦哆,Stub的onTransact方法會被調(diào)用谤祖,進(jìn)而調(diào)用真正的add方法。
在這里老速,我們就可以看到Parcel的一系列 writexxx粥喜、readxxx方法的作用。
public interface ICalculateInterface extends IInterface {
public abstract class Stub extends Binder implements ICalculateInterface {
private static final String DESCRIPTOR = "com.sososeen09.knowledge.ipc.handipc.ICalculateInterface";
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
@Override
public IBinder asBinder() {
return this;
}
public static ICalculateInterface asInterface(IBinder obj) {
if (obj == null) {
return null;
}
IInterface iInterface = obj.queryLocalInterface(DESCRIPTOR);
if (iInterface != null && iInterface instanceof ICalculateInterface) {
return (ICalculateInterface) iInterface;
} else {
return new Proxy(obj);
}
}
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_add: {
data.enforceInterface(DESCRIPTOR);
int _arg0, _arg1, _result;
_arg0 = data.readInt();
_arg1 = data.readInt();
_result = this.add(_arg0, _arg1);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements ICalculateInterface {
private IBinder mRemote;
public Proxy(IBinder remote) {
mRemote = remote;
}
public String getInterfaceDescriptor() {
return DESCRIPTOR;
}
@Override
public int add(int a, int b) throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
int result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(a);
_data.writeInt(b);
mRemote.transact(TRANSACTION_add, _data, _reply, 0);
_reply.readException();
result = _reply.readInt();
} finally {
_data.recycle();
_reply.recycle();
}
return result;
}
@Override
public IBinder asBinder() {
return mRemote;
}
}
}
static final int TRANSACTION_add = (IBinder.FIRST_CALL_TRANSACTION + 0);
int add(int a, int b) throws RemoteException;
}
關(guān)于Binder的介紹和Java層的使用橘券,先介紹到這里额湘,這些內(nèi)容會持續(xù)更新卿吐。
Binder使用的一些注意事項(xiàng)
- Binder方法是在Binder線程池中被調(diào)用的,所以不需要再次new一個線程了锋华,Client調(diào)用Server端方法嗡官,當(dāng)前線程會被調(diào)起,太耗時的話記得用一個線程來調(diào)用毯焕。
- Intent攜帶的數(shù)據(jù)大小是限制了衍腥,不要超過1M,否則就會報(bào)一個TransactionTooLargeException的異常纳猫。這是因?yàn)锽inder數(shù)據(jù)的緩存大小就是1M婆咸。有的時候,即使一次攜帶的數(shù)據(jù)不到1M芜辕,還是可能會報(bào)異常尚骄,因?yàn)榇嬖诓l(fā)的情況。
參考
- Android進(jìn)程間通信(IPC)機(jī)制Binder簡要介紹和學(xué)習(xí)計(jì)劃
- Android Bander設(shè)計(jì)與實(shí)現(xiàn) - 設(shè)計(jì)篇
- 田維術(shù)-Binder學(xué)習(xí)指南
- 小米系統(tǒng)工程師gityuan——Binder系列
- Android面試一天一題(Day 35:神秘的Binder機(jī)制
- Binder 總體架構(gòu)及相關(guān)代碼淺析
- Android Binder之應(yīng)用層總結(jié)與分析
- Android Binder之應(yīng)用層精彩解析