淺談Android進(jìn)程間通訊(Binder)
進(jìn)程間通訊IPC不是Android中所特有的感憾,任何一個(gè)操作系統(tǒng)需要相應(yīng)的IPC機(jī)制膘婶。理解Binder對(duì)于理解整個(gè)Android系統(tǒng)有著非常重要的作用折汞,Android系統(tǒng)的四大組件会放,AMS,PMS等系統(tǒng)服務(wù)無(wú)一不與Binder掛鉤诫隅。
本文主要從以下幾個(gè)方面講解Binder:
- linux進(jìn)程間通信相關(guān)背景知識(shí)
- 圖解Binder通信模型
- Java層的Binder
- AIDL使用詳解
- serverManager進(jìn)程與client進(jìn)程或server進(jìn)程的交互
1. linux進(jìn)程間通信相關(guān)知識(shí)
進(jìn)程隔離
進(jìn)程隔離是為保護(hù)操作系統(tǒng)中進(jìn)程互不干擾而設(shè)計(jì)的一組不同硬件和軟件的技術(shù)腐魂。這個(gè)技術(shù)是為了避免進(jìn)程A寫入進(jìn)程B的情況發(fā)生。 進(jìn)程的隔離實(shí)現(xiàn)逐纬,使用了虛擬地址空間蛔屹。進(jìn)程A的虛擬地址和進(jìn)程B的虛擬地址不同,這樣就防止進(jìn)程A將數(shù)據(jù)信息寫入進(jìn)程B豁生。
用戶空間/內(nèi)核空間
Linux Kernel 是操作系統(tǒng)的核心兔毒,獨(dú)立于普通的應(yīng)用程序,可以訪問(wèn)受保護(hù)的內(nèi)存空間甸箱,也有訪問(wèn)底層硬件設(shè)備的所有權(quán)限育叁。
對(duì)于Kernel這么一個(gè)高安全級(jí)別的東西,顯然是不容許其它的應(yīng)用程序隨便調(diào)用或訪問(wèn)的芍殖,所以需要對(duì)Kernel提供一定的保護(hù)機(jī)制擂红,這個(gè)保護(hù)機(jī)制用來(lái)告訴那些應(yīng)用程序,你只可以訪問(wèn)某些許可的資源围小,不許可的資源是拒絕被訪問(wèn)的昵骤,于是就把Kernel和上層的應(yīng)用程序抽像的隔離開(kāi),分別稱之為Kernel Space和User Space肯适。
內(nèi)核模塊/驅(qū)動(dòng)
通過(guò)系統(tǒng)調(diào)用变秦,用戶空間可以訪問(wèn)內(nèi)核空間,那么如果一個(gè)用戶空間想與另外一個(gè)用戶空間進(jìn)行通信怎么辦呢框舔?很自然想到的是讓操作系統(tǒng)內(nèi)核添加支持蹦玫;傳統(tǒng)的Linux通信機(jī)制赎婚,比如Socket,管道等都是內(nèi)核支持的樱溉;但是Binder并不是Linux內(nèi)核的一部分挣输,它是怎么做到訪問(wèn)內(nèi)核空間的呢?Linux的動(dòng)態(tài)可加載內(nèi)核模塊(Loadable Kernel Module福贞,LKM)機(jī)制解決了這個(gè)問(wèn)題撩嚼;模塊是具有獨(dú)立功能的程序,它可以被單獨(dú)編譯挖帘,但不能獨(dú)立運(yùn)行完丽。它在運(yùn)行時(shí)被鏈接到內(nèi)核作為內(nèi)核的一部分在內(nèi)核空間運(yùn)行。這樣拇舀,Android系統(tǒng)可以通過(guò)添加一個(gè)內(nèi)核模塊運(yùn)行在內(nèi)核空間逻族,用戶進(jìn)程之間的通過(guò)這個(gè)模塊作為橋梁,就可以完成通信了骄崩。
在Android系統(tǒng)中聘鳞,這個(gè)運(yùn)行在內(nèi)核空間的,負(fù)責(zé)各個(gè)用戶進(jìn)程通過(guò)Binder通信的內(nèi)核模塊叫做Binder驅(qū)動(dòng);
驅(qū)動(dòng)程序一般指的是設(shè)備驅(qū)動(dòng)程序(Device Driver)要拂,是一種可以使計(jì)算機(jī)和設(shè)備通信的特殊程序抠璃。相當(dāng)于硬件的接口,操作系統(tǒng)只有通過(guò)這個(gè)接口宇弛,才能控制硬件設(shè)備的工作鸡典;
驅(qū)動(dòng)就是操作硬件的接口源请,為了支持Binder通信過(guò)程枪芒,Binder使用了一種“硬件”,因此這個(gè)模塊被稱之為驅(qū)動(dòng)
2. Binder通信模型
其中Server谁尸,Client舅踪,SMgr運(yùn)行于用戶空間,驅(qū)動(dòng)運(yùn)行于內(nèi)核空間良蛮。
整個(gè)通信步驟如下:
I. SM建立(建立通信錄)抽碌;首先有一個(gè)進(jìn)程向驅(qū)動(dòng)提出申請(qǐng)為SM;驅(qū)動(dòng)同意之后决瞳,SM進(jìn)程負(fù)責(zé)管理Service(注意這里是Service而不是Server货徙,因?yàn)槿绻ㄐ胚^(guò)程反過(guò)來(lái)的話,那么原來(lái)的客戶端Client也會(huì)成為服務(wù)端Server)不過(guò)這時(shí)候通信錄還是空的皮胡,一個(gè)號(hào)碼都沒(méi)有痴颊。
II. 各個(gè)Server向SM注冊(cè)(完善通信錄);每個(gè)Server端進(jìn)程啟動(dòng)之后屡贺,向SM報(bào)告蠢棱,我是zhangsan, 要找我請(qǐng)返回0x1234(這個(gè)地址沒(méi)有實(shí)際意義锌杀,類比);其他Server進(jìn)程依次如此泻仙;這樣SM就建立了一張表糕再,對(duì)應(yīng)著各個(gè)Server的名字和地址;就好比B與A見(jiàn)面了玉转,說(shuō)存?zhèn)€我的號(hào)碼吧吟宦,以后找我撥打10086牍疏;
III. Client想要與Server通信,首先詢問(wèn)SM;請(qǐng)告訴我如何聯(lián)系z(mì)hangsan舰蟆,SM收到后給他一個(gè)號(hào)碼0x1234;Client收到之后声滥,開(kāi)心滴用這個(gè)號(hào)碼撥通了Server的電話呜叫,于是就開(kāi)始通信了。
Binder驅(qū)動(dòng)保存了每一個(gè)跨越進(jìn)程的Binder對(duì)象的相關(guān)信息怠李;在驅(qū)動(dòng)中圾叼,Binder本地對(duì)象的代表是一個(gè)叫做binder_node的數(shù)據(jù)結(jié)構(gòu),
Binder代理對(duì)象是用binder_ref代表的捺癞。
3. java層Binder
1. AIDL過(guò)程分析
首先server端的aidl接口
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
void registerListener(in OnNewBookArrivedListener onNewBookArrivedListener);
void unregisterListener(in OnNewBookArrivedListener onNewBookArrivedListener);
}
編譯之后系統(tǒng)生成IBookManager
package com.chehejia.aidlserver;
public interface IBookManager extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.chehejia.aidlserver.IBookManager {
private static final java.lang.String DESCRIPTOR = "com.chehejia.aidlserver.IBookManager";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.chehejia.aidlserver.IBookManager interface,
* generating a proxy if needed.
*/
public static com.chehejia.aidlserver.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.chehejia.aidlserver.IBookManager))) {
return ((com.chehejia.aidlserver.IBookManager) iin);
}
return new com.chehejia.aidlserver.IBookManager.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_getBookList: {
data.enforceInterface(DESCRIPTOR);
java.util.List<com.chehejia.aidlserver.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(DESCRIPTOR);
com.chehejia.aidlserver.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.chehejia.aidlserver.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_registerListener: {
data.enforceInterface(DESCRIPTOR);
com.chehejia.aidlserver.OnNewBookArrivedListener _arg0;
_arg0 = com.chehejia.aidlserver.OnNewBookArrivedListener.Stub.asInterface(data.readStrongBinder());
this.registerListener(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_unregisterListener: {
data.enforceInterface(DESCRIPTOR);
com.chehejia.aidlserver.OnNewBookArrivedListener _arg0;
_arg0 = com.chehejia.aidlserver.OnNewBookArrivedListener.Stub.asInterface(data.readStrongBinder());
this.unregisterListener(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.chehejia.aidlserver.IBookManager {
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 java.util.List<com.chehejia.aidlserver.Book> getBookList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.chehejia.aidlserver.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.chehejia.aidlserver.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void addBook(com.chehejia.aidlserver.Book book) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book != null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public void registerListener(com.chehejia.aidlserver.OnNewBookArrivedListener onNewBookArrivedListener) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeStrongBinder((((onNewBookArrivedListener != null)) ? (onNewBookArrivedListener.asBinder()) : (null)));
mRemote.transact(Stub.TRANSACTION_registerListener, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public void unregisterListener(com.chehejia.aidlserver.OnNewBookArrivedListener onNewBookArrivedListener) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeStrongBinder((((onNewBookArrivedListener != null)) ? (onNewBookArrivedListener.asBinder()) : (null)));
mRemote.transact(Stub.TRANSACTION_unregisterListener, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_registerListener = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
static final int TRANSACTION_unregisterListener = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
}
public java.util.List<com.chehejia.aidlserver.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.chehejia.aidlserver.Book book) throws android.os.RemoteException;
public void registerListener(com.chehejia.aidlserver.OnNewBookArrivedListener onNewBookArrivedListener) throws android.os.RemoteException;
public void unregisterListener(com.chehejia.aidlserver.OnNewBookArrivedListener onNewBookArrivedListener) throws android.os.RemoteException;
}
2. IBinder/IInterface/Binder/BinderProxy/Stub總結(jié)
IBinder是一個(gè)接口夷蚊,它代表了一種跨進(jìn)程傳輸?shù)哪芰Γ恢灰獙?shí)現(xiàn)了這個(gè)接口髓介,就能將這個(gè)對(duì)象進(jìn)行跨進(jìn)程傳遞惕鼓;這是驅(qū)動(dòng)底層支持的;在跨進(jìn)程數(shù)據(jù)流經(jīng)驅(qū)動(dòng)的時(shí)候唐础,驅(qū)動(dòng)會(huì)識(shí)別IBinder類型的數(shù)據(jù)箱歧,從而自動(dòng)完成不同進(jìn)程Binder本地對(duì)象以及Binder代理對(duì)象的轉(zhuǎn)換。
IBinder負(fù)責(zé)數(shù)據(jù)傳輸一膨,那么client與server端的調(diào)用契約(這里不用接口避免混淆)呢呀邢?這里的IInterface代表就是遠(yuǎn)程server對(duì)象具有什么能力。具體來(lái)說(shuō)豹绪,就是aidl里面的接口价淌。
Java層的Binder類,代表的其實(shí)就是Binder本地對(duì)象瞒津。BinderProxy類是Binder類的一個(gè)內(nèi)部類蝉衣,它代表遠(yuǎn)程進(jìn)程的Binder對(duì)象的本地代理;這兩個(gè)類都繼承自IBinder, 因而都具有跨進(jìn)程傳輸?shù)哪芰ο矧剑粚?shí)際上病毡,在跨越進(jìn)程的時(shí)候,Binder驅(qū)動(dòng)會(huì)自動(dòng)完成這兩個(gè)對(duì)象的轉(zhuǎn)換钓辆。
在使用AIDL的時(shí)候剪验,編譯工具會(huì)給我們生成一個(gè)Stub的靜態(tài)內(nèi)部類肴焊;這個(gè)類繼承了Binder, 說(shuō)明它是一個(gè)Binder本地對(duì)象,它實(shí)現(xiàn)了IInterface接口功戚,表明它具有遠(yuǎn)程Server承諾給 Client的能力娶眷;Stub是一個(gè)抽象類,具體的IInterface的相關(guān)實(shí)現(xiàn)需要我們手動(dòng)完成啸臀,這里使用了策略模式届宠。
4 AIDL中的注意點(diǎn)
- 客戶端注冊(cè)的listener如果服務(wù)端采用普通list存儲(chǔ)的話,會(huì)造成取消注冊(cè)失敗乘粒。原因是客戶端注冊(cè)的listener經(jīng)過(guò)Binder傳輸?shù)椒?wù)端后豌注,會(huì)在服務(wù)端生成新的listener,導(dǎo)致客戶端注冊(cè)的和解綁的不是同一個(gè)對(duì)象灯萍。那么我們應(yīng)該使用什么方式進(jìn)行取消注冊(cè)的操作呢轧铁?答案是 RemoteCallbackList。
客戶端調(diào)用遠(yuǎn)程服務(wù)端的方法旦棉,被調(diào)用的方法運(yùn)行在服務(wù)端的 Binder線程池中齿风,同時(shí)客戶端當(dāng)前調(diào)用線程被掛起,如果服務(wù)端的方法有耗時(shí)的操作绑洛,那么客戶端的調(diào)用線程必須為子線程來(lái)避免ANR救斑。
Binder可能會(huì)意外的死亡,需要客戶端重新連接服務(wù)真屯。這里有兩種方法脸候,第一種是給Binder設(shè)置DeathRecipient監(jiān)聽(tīng),binderDied回調(diào)(非UI線程)绑蔫。第二種是onserverDisconnected回調(diào)(UI線程)运沦。
5. ServiceManager與Binder
在Android啟動(dòng)ServiceManager進(jìn)程的時(shí)候,都做了什么事晾匠?
- 利用BINDER_SET_CONTEXT_MGR命令茶袒,令自己成為上下文管理者梯刚,其實(shí)也就是成為ServiceManager凉馆。
- 將BINDER_SET_CONTEXT_MGR命令傳給Binder驅(qū)動(dòng)的時(shí)候,Binder驅(qū)動(dòng)就會(huì)為其在內(nèi)核空間中創(chuàng)建一個(gè)節(jié)點(diǎn)(binder_node)亡资,句柄為0澜共。(0號(hào)引用)在整個(gè)系統(tǒng)中,只會(huì)有一個(gè)binder_context_mgr_node锥腻,所以也只會(huì)有一個(gè)ServiceManager的進(jìn)程嗦董,那么對(duì)ServiceManager的訪問(wèn),驅(qū)動(dòng)就可以在系統(tǒng)中定義好其句柄瘦黑,也就是 0京革。
- 進(jìn)入一個(gè)無(wú)限循環(huán)奇唤,等待Client的請(qǐng)求到來(lái)。
server進(jìn)程注冊(cè)到serverManager
- 每一個(gè)提供服務(wù)的Server都會(huì)通過(guò)Binder驅(qū)動(dòng)匹摇,將自身給注冊(cè)到ServiceManager中咬扇。(本質(zhì)上,就是Server們會(huì)將自身作為一個(gè)對(duì)象廊勃,封裝在數(shù)據(jù)包中懈贺,將這些數(shù)據(jù)復(fù)制到內(nèi)核空間中,由Binder驅(qū)動(dòng)訪問(wèn)坡垫。)
- Binder驅(qū)動(dòng)讀取數(shù)據(jù)包的時(shí)候梭灿,如果發(fā)現(xiàn)其中有Binder實(shí)體,那么也會(huì)為對(duì)應(yīng)的Binder實(shí)體創(chuàng)建對(duì)應(yīng)的Binder節(jié)點(diǎn)(BinderNode)冰悠。
- Binder驅(qū)動(dòng)也會(huì)為這些服務(wù)分配句柄(大于0)堡妒,同時(shí)會(huì)將這些句柄也記錄在Binder驅(qū)動(dòng)中,然后再將這些句柄和名字發(fā)送給ServiceManager溉卓,由ServiceManager來(lái)維護(hù)涕蚤。
Client進(jìn)程與serverManager交互
- server服務(wù)的名字,加上一個(gè)句柄為 0 的值的诵,封裝為一個(gè)數(shù)據(jù)包万栅,打開(kāi)Binder設(shè)備文件,將這個(gè)數(shù)據(jù)發(fā)送給Binder驅(qū)動(dòng)西疤。
- Binder驅(qū)動(dòng)接收到句柄為0烦粒,就會(huì)將這數(shù)據(jù)包扔給ServiceManager。
- ServiceManager接收到這個(gè)數(shù)據(jù)包代赁,就會(huì)分析扰她,發(fā)現(xiàn)是要找某個(gè)名字的服務(wù),于是就找找找芭碍,然后將對(duì)應(yīng)服務(wù)的句柄發(fā)送回來(lái)(某大于0的句柄)徒役。
- 驅(qū)動(dòng)再server進(jìn)程句柄發(fā)回給Client。
- Client獲取句柄之后窖壕,就會(huì)再加上想要的服務(wù)忧勿,還有這個(gè)句柄,再發(fā)送給Binder驅(qū)動(dòng)瞻讽,Binder驅(qū)動(dòng)就會(huì)找到對(duì)應(yīng)的句柄鸳吸,然后調(diào)用server進(jìn)程相關(guān)服務(wù)。