Binder機(jī)制是Android系統(tǒng)中最主要的進(jìn)程間通信機(jī)制。雖然Android底層使用的是linux內(nèi)核伐割,但是除了匿名管道,socket外,Android并沒(méi)有使用linux中的命名管道萨惑,信號(hào)量,消息隊(duì)列等傳統(tǒng)的IPC通信方式仇矾。Binder猶如一張大網(wǎng)庸蔼,將Android整個(gè)系統(tǒng)中的組件,跨進(jìn)程的組織在一起贮匕。淡化進(jìn)程姐仅,強(qiáng)化組件是Android的一個(gè)設(shè)計(jì)理念。在這個(gè)過(guò)程中刻盐,Binder發(fā)揮了巨大作用掏膏。
binder 的作用
binder的作用可以概括為兩點(diǎn):
IPC:是一種跨進(jìn)程通信手段,但是傳輸數(shù)據(jù)的大小受限制敦锌,不建議傳輸較大數(shù)據(jù)馒疹。
RPC:是一種遠(yuǎn)程過(guò)程調(diào)用手段,即一個(gè)進(jìn)程中可以透明的調(diào)用另外一個(gè)進(jìn)程中的方法乙墙。
兩者經(jīng)常相互伴隨颖变,例如跨進(jìn)程調(diào)用的時(shí)候,參數(shù)的傳遞以及結(jié)果的傳遞等听想。
binder機(jī)制簡(jiǎn)述
binder實(shí)體對(duì)象: 就是binder服務(wù)的提供者腥刹,一個(gè)提供binder服務(wù)的類必須繼承BBinder類(native層),因此binder實(shí)體對(duì)象又叫做BBinder對(duì)象汉买。
binder引用對(duì)象: 是binder服務(wù)提供者在客戶端進(jìn)程的代表肛走,每個(gè)引用對(duì)象類型必須繼承BpBinder類(native層),因此binder引用對(duì)象有叫做BpBinder對(duì)象。
binder代理對(duì)象: 代理對(duì)象簡(jiǎn)單理解為內(nèi)聚了一個(gè)binder引用對(duì)象朽色,因此可以通過(guò)這個(gè)內(nèi)聚的引用對(duì)象發(fā)起RPC調(diào)用邻吞。可以有多個(gè)不同的代理對(duì)象葫男,但卻內(nèi)聚了同一個(gè)引用對(duì)象抱冷。
IBinder對(duì)象: BBinder和BpBinder都是繼承自IBinder.因此binder實(shí)體對(duì)象和binder引用對(duì)象都可以稱為IBinder對(duì)象∩液郑可以通過(guò)IBinder.queryLocalInterface()方法來(lái)判斷到底是binder實(shí)體對(duì)象還是binder引用對(duì)象旺遮。
binder跨進(jìn)程傳輸?shù)臄?shù)據(jù)類型是Parcel。
以RPC為例的示意圖如下:
通過(guò)一個(gè)運(yùn)行在內(nèi)核空間的binder驅(qū)動(dòng)進(jìn)程盈咳,將兩個(gè)用戶空間的進(jìn)程聯(lián)系了起來(lái)耿眉。為解決由于用戶空間進(jìn)程之間虛擬地址相互獨(dú)立而引起的無(wú)法跨進(jìn)程調(diào)用別的進(jìn)程中的對(duì)象方法的難題帶來(lái)了曙光。
通過(guò)BInder引用對(duì)象發(fā)起RPC調(diào)用
假設(shè)App中的MainActivity中以startActivityForResult()方法啟動(dòng)了該App中的Main2Activity鱼响,那么Main2Activity可以通過(guò)AMS這個(gè)binder服務(wù)中的getCallingActivity()方法查詢是誰(shuí)啟動(dòng)了自己鸣剪。
AMS.getCallingActivity():
public ComponentName getCallingActivity(IBinder token)
去參數(shù)是Activity.mToken. 可以通過(guò)反射從客戶端Activity組件中獲取。
1). 獲得AMS的引用binder
AMS作為一個(gè)系統(tǒng)服務(wù)丈积,在Android系統(tǒng)啟動(dòng)過(guò)程中筐骇,會(huì)將自己注冊(cè)到ServiceManager中(注冊(cè)過(guò)程以后在分析)。現(xiàn)在只要知道客戶端可以通過(guò)ServiceManager來(lái)獲得AMS的引用binder即可江滨。
2). 使用引用binder進(jìn)行RPC調(diào)用時(shí)铛纬,需要直到要調(diào)用的方法的編號(hào),這個(gè)編號(hào)可以從Android源碼中獲取唬滑。
在Android 6.0 中該方法編號(hào)如下:
int GET_CALLING_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+21;
服務(wù)端根據(jù)這個(gè)編號(hào)告唆,執(zhí)行相應(yīng)的邏輯。
3). 方法參數(shù)的傳遞
binder中唯一能的傳遞的數(shù)據(jù)結(jié)構(gòu)就是Parcel晶密,所以必須將方法的參數(shù)打包到Parcel中悔详。針對(duì)不同的數(shù)據(jù)類型Parcle提供了不同的方法,來(lái)將這些對(duì)應(yīng)的數(shù)據(jù)打入Parcel中惹挟。
另外還要準(zhǔn)備一個(gè)Parcel用于接收服務(wù)端的返回?cái)?shù)據(jù)。
4). 發(fā)起RPC調(diào)用
通過(guò)引用對(duì)象的transact()方法缝驳,發(fā)起RPC調(diào)用连锯,絕大多數(shù)情況下,此調(diào)用是一個(gè)同步調(diào)用用狱,也就是說(shuō)會(huì)一直阻塞到服務(wù)端將數(shù)據(jù)返回為止运怖。
但是當(dāng)transact()方法中傳入的flag為FLAG_ONEWAY時(shí),方法會(huì)立即返回夏伊,不會(huì)等到服務(wù)端返回?cái)?shù)據(jù)摇展。
該方法會(huì)最終將上述信息傳入binder驅(qū)動(dòng)中去。
5). 客戶端從返回的Parcel中讀取數(shù)據(jù)
服務(wù)端中將請(qǐng)求的方法執(zhí)行完畢之后溺忧,將方法返回值打入到parcel中咏连,binder驅(qū)動(dòng)將其返回到客戶端組件中盯孙,然后客戶端組件按照調(diào)用方法的返回值類型,從返回的parcel中讀取即可祟滴。
public class Main2Activity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
// 拿到AMS的引用對(duì)象
IBinder sm = (IBinder) Reflect.on("android.os.ServiceManager").call("getService","activity").get();
// 拿到方法編號(hào)
int funCode = IBinder.FIRST_CALL_TRANSACTION+21;
// 準(zhǔn)備方法參數(shù)數(shù)據(jù)
Parcel data = Parcel.obtain();
// 首先要寫(xiě)入的數(shù)據(jù)必須是binder服務(wù)端的descriptor
data.writeInterfaceToken("android.app.IActivityManager");
// 接下來(lái)是方法的參數(shù)
data.writeStrongBinder((IBinder)Reflect.on(this).field("mToken").get());
// 用于接受返回?cái)?shù)據(jù)
Parcel reply = Parcel.obtain();
// 發(fā)起RPC調(diào)用振惰,同步調(diào)用,直到調(diào)用結(jié)束垄懂,期間一直阻塞
try {
sm.transact(funCode,data,reply,0);
} catch (RemoteException e) {
e.printStackTrace();
}
// 讀取返回?cái)?shù)據(jù)
reply.readException();
// 解析返回?cái)?shù)據(jù)
ComponentName res = ComponentName.readFromParcel(reply);
// 回收parcle
data.recycle();
reply.recycle();
Log.i("shajia","calling Activity name: "+res.getClassName());
}
}
通過(guò)binder代理對(duì)象發(fā)起RPC操作
對(duì)于前面例子中獲取調(diào)用者的情況骑晶,實(shí)際開(kāi)發(fā)中都是通過(guò)Activiy.getCallingActivity()來(lái)獲取的:
public ComponentName getCallingActivity() {
try {
return ActivityManagerNative.getDefault().getCallingActivity(mToken);
} catch (RemoteException e) {
return null;
}
}
其中ActivityManagerNative.getDefault()返回的是ActivityManagerProxy對(duì)象。
ActivityManagerProxy是AMS的binder代理類草慧。
binder代理對(duì)象通過(guò)內(nèi)聚的binder引用對(duì)象間接發(fā)起RPC操作桶蛔。對(duì)于系統(tǒng)服務(wù)來(lái)說(shuō),它的binder代理對(duì)象都是事先定義好的漫谷。binder代理對(duì)象還要實(shí)現(xiàn)服務(wù)接口仔雷,實(shí)際上就是對(duì)binder引用對(duì)象發(fā)起RPC操作的二次封裝。
class ActivityManagerProxy implements IActivityManager
{
...............
public ComponentName getCallingActivity(IBinder token)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(token);
mRemote.transact(GET_CALLING_ACTIVITY_TRANSACTION, data, reply, 0);
reply.readException();
ComponentName res = ComponentName.readFromParcel(reply);
data.recycle();
reply.recycle();
return res;
}
...............
可以看到代理類中已經(jīng)幫我們封裝好了getCallingActivity()的操作抖剿。因?yàn)橹苯油ㄟ^(guò)binder引用發(fā)起RPC操作的話朽寞,需要開(kāi)發(fā)者知道方法的編號(hào),而方法的編號(hào)又是隨著Android版本的變化而可能發(fā)生改變的斩郎。所以一般來(lái)說(shuō)都會(huì)為binder服務(wù)封裝一個(gè)binder代理類脑融。這樣做還有一個(gè)好處是通過(guò)一個(gè)binder引用對(duì)象,可以創(chuàng)建多個(gè)binder代理對(duì)象缩宜。
binder服務(wù)分類
分為兩大類:
向ServiceManager注冊(cè)的binder服務(wù)
Android系統(tǒng)中自帶的絕大多數(shù)服務(wù)肘迎,例如AMS,PMS等都會(huì)向ServiceManager注冊(cè),注冊(cè)時(shí)會(huì)傳入一個(gè)service名字锻煌,例如AMS注冊(cè)是傳入的是“activity”妓布。
客戶端可以通過(guò)名字向ServiceManager查詢對(duì)象的binder服務(wù),ServiceManager會(huì)返回一個(gè)IBinder對(duì)象宋梧。
至于說(shuō)返回的IBinder對(duì)象究竟是實(shí)體bidner呢還是引用binder匣沼,按照下面的規(guī)則決定:
當(dāng)binder服務(wù)端同進(jìn)程請(qǐng)求該服務(wù)時(shí),返回的是binder實(shí)體對(duì)象捂龄。
當(dāng)請(qǐng)求者與binder服務(wù)端在一個(gè)進(jìn)程時(shí)释涛,返回的是引用對(duì)象。
沒(méi)有向ServiceManager注冊(cè)的的bidner服務(wù)倦沧,又被稱為匿名binder服務(wù)
典型代表就是App中通過(guò)aidl實(shí)現(xiàn)的service組件唇撬。
aidl實(shí)際上幫我們完整實(shí)現(xiàn)了服務(wù)代理類,以及是是實(shí)現(xiàn)了binder服務(wù)類中與語(yǔ)義處理相關(guān)的所有操作展融,例如方法編號(hào)的分配窖认,方法參數(shù)從parcel中的提取,以及返回值打入parcel中等所有的操作。開(kāi)發(fā)者只需要實(shí)現(xiàn)服務(wù)接口即可扑浸。
因?yàn)閍pp是沒(méi)有權(quán)限向ServiceManager注冊(cè)服務(wù)的烧给,那么怎么獲取app中的額service實(shí)體的引用binder呢?首装?创夜?
那就是直接傳遞binder實(shí)體,將binder實(shí)體對(duì)象打入到Parcel中仙逻,跨進(jìn)程傳入到AMS中去驰吓。
Binder實(shí)體在Binder驅(qū)動(dòng)中的傳輸,會(huì)被特殊處理系奉,最終返回到AMS中的是一個(gè)binder引用對(duì)象檬贰。(詳細(xì)過(guò)程后續(xù)在分析嘍!H绷痢N痰印)
其他App進(jìn)程中的組件便可以通過(guò)AMS拿到要請(qǐng)求的service服務(wù)的binder引用對(duì)象了。要注意的是萌踱,此過(guò)程中是AMS將所請(qǐng)求的binder服務(wù)的引用對(duì)象打入Parcel,然后通過(guò)Binder驅(qū)動(dòng)傳遞到請(qǐng)求者進(jìn)程中葵礼。
簡(jiǎn)單的說(shuō)就是Binder實(shí)體對(duì)象的傳遞過(guò)程,伴隨著binder服務(wù)在Binder驅(qū)動(dòng)中相關(guān)數(shù)據(jù)結(jié)構(gòu)初始化以及binder引用對(duì)象的創(chuàng)建過(guò)程并鸵。
不通過(guò)aidl實(shí)現(xiàn)一個(gè)service,來(lái)熟悉一下整個(gè)過(guò)程:
1. 首先頂定義一個(gè)服務(wù)接口鸳粉,即服務(wù)要對(duì)外提供哪些方法。
IBinderService.java:
public interface IBinderService extends IInterface {
String getMessage();
/**
* 服務(wù)的描述园担,客戶端在使用parcel跨進(jìn)程傳輸數(shù)據(jù)的時(shí)候
* 必須首先寫(xiě)入服務(wù)的描述届谈,即該數(shù)據(jù)是發(fā)給哪個(gè)binder的。
* 將來(lái)服務(wù)端收到數(shù)據(jù)后會(huì)檢查這個(gè)服務(wù)描述是否和自己的一致弯汰,不一致就不做處理了
*/
String DESCRIPITON = "MyBinderService";
// 定義方法編號(hào)
int GET_MESSAGE = IBinder.FIRST_CALL_TRANSACTION+0;
}
binder服務(wù)接口必須繼承自IInterface接口艰山,該接口中只有一個(gè)方法:
public IBinder asBinder()
binder服務(wù)接口一般要包含三部分內(nèi)容:
首先是該binder服務(wù)的描述DESCRIPITON,發(fā)送數(shù)據(jù)時(shí)必須先發(fā)送該描述咏闪,接收數(shù)據(jù)時(shí)必須先對(duì)該描述進(jìn)行檢查曙搬,和自己不匹配的話那就不用繼續(xù)進(jìn)行了。
然后是方法編號(hào)鸽嫂,這個(gè)編號(hào)實(shí)際含義要在binder實(shí)體對(duì)象中的onTransact()方法中才能體現(xiàn)出來(lái)纵装。可以理解為onTransact()根據(jù)這個(gè)編號(hào)調(diào)用不同的處理分支溪胶。
最后就是該服務(wù)對(duì)外提供的具體方法聲明了。
binder實(shí)體端和代理對(duì)象端必須都繼承這個(gè)服務(wù)接口稳诚,并實(shí)現(xiàn)其中的方法哗脖。另外服務(wù)端還要重載binder的onTransact()方法。
2. 實(shí)現(xiàn)bidner服務(wù)端
binder服務(wù)端的重點(diǎn)在于實(shí)現(xiàn)onTransact()方法,該方法中會(huì)依據(jù)客戶端傳入的方法編號(hào)才避,調(diào)用恰當(dāng)?shù)姆种нM(jìn)行處理橱夭。
處理過(guò)程也是很簡(jiǎn)單的,就是從parcel中解析參數(shù)桑逝,調(diào)用對(duì)應(yīng)的方法執(zhí)行棘劣,將執(zhí)行結(jié)果打入parcel中。
public class BinderServiceStub extends Binder implements IBinderService {
public BinderServiceStub(){
// 調(diào)用該方法后binder實(shí)體端的binder.queryLocalInterface()
// 返回就不會(huì)為null楞遏。
attachInterface(this,DESCRIPITON);
}
// 實(shí)現(xiàn)服務(wù)接口方法
public String getMessage() {
return " i am from Message!!!!!";
}
/**
* 顧名思義茬暇,將自身轉(zhuǎn)換為一個(gè)IBinder對(duì)象,
* 因?yàn)锽inder繼承子Binder,Binder繼承自IBinder
*/
@Override
public IBinder asBinder() {
return this;
}
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code){
case GET_MESSAGE:{
// 客戶端先發(fā)送的是服務(wù)描述寡喝,所以這里先接收服務(wù)描述并判斷是否和自己一致
data.enforceInterface(DESCRIPITON);
// 開(kāi)始執(zhí)行客戶端請(qǐng)求的服務(wù)端的方法
String msg = getMessage();
// 將結(jié)果打入Parcel
reply.writeNoException();
reply.writeString(msg);
return true;
}
}
/**
* 必須調(diào)用父類onTransact處理其他code
*/
return super.onTransact(code, data, reply, flags);
}
}
實(shí)現(xiàn)binder代理端
前面介紹了糙俗,binder代理對(duì)象類會(huì)內(nèi)聚一個(gè)binder引用對(duì)象,該引用對(duì)象通過(guò)構(gòu)造方法傳入即可:
public class BinderServiceProxy implements IBinderService {
/**
* 內(nèi)聚的binder引用對(duì)象
*/
private IBinder remote;
public BinderServiceProxy(IBinder binder){
if(binder.queryLocalInterface(DESCRIPITON) == null )
remote = binder;
else
throw new RuntimeException(" this is not a BpBinder.");
}
@Override
public String getMessage() {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(DESCRIPITON);
try {
remote.transact(GET_MESSAGE,data,reply,0);
} catch (RemoteException e) {
e.printStackTrace();
}
reply.readException();
String msg = reply.readString();
data.recycle();
data.recycle();
return msg;
}
@Override
public IBinder asBinder() {
return null;
}
}
這里要特別注意的是,因?yàn)閎inder代理對(duì)象類內(nèi)聚的是一個(gè)binder引用對(duì)象预鬓,所以要對(duì)構(gòu)造方法中傳入的Ibinder對(duì)象進(jìn)行檢查巧骚,保證其是binder引用對(duì)象。
然后就是實(shí)現(xiàn)服務(wù)接口方法格二,這里很簡(jiǎn)單了劈彪,就是組裝Parcel數(shù)據(jù),然后利用引用binder對(duì)象發(fā)起RPC調(diào)用顶猜。
4. 獲取binder引用對(duì)象
這就要借助Android中的service組件了沧奴,并且以bindService()方法啟動(dòng)該service。
首先定義service組件:
public class MyBinderService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new BinderServiceStub();
}
}
然后在清單文件中驶兜,聲明該service組件扼仲,并且設(shè)置process屬性,保證該service運(yùn)行在另外一個(gè)進(jìn)程中:
<service android:name="com.godin.studydemo.MyBinderService"
android:process=":S0"/>
最后以bindService()方式綁定該service:
Intent intent = new Intent();
intent.setClass(this,MyBinderService.class);
bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if(service.queryLocalInterface(IBinderService.DESCRIPITON)==null){
// 得到binder代理對(duì)象
BinderServiceProxy proxy = new BinderServiceProxy(service);
// 開(kāi)始執(zhí)行方法
Log.i("shajia","message: "+proxy.getMessage());
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
},BIND_AUTO_CREATE);
binder 安全基礎(chǔ)
binder服務(wù)端可以通過(guò)binder提供的兩個(gè)api拿到客戶端的uid和pid抄淑,從而決定是否要對(duì)這次請(qǐng)求進(jìn)行處理:
getCallingUid();
getCallingPid();
binder 死亡通知機(jī)制
當(dāng)端服務(wù)進(jìn)程掛掉的時(shí)候屠凶,客戶端是有必要知道的,可以通過(guò)binder引用對(duì)象的linkToDeath()方法來(lái)設(shè)置binder服務(wù)死亡監(jiān)聽(tīng)機(jī)制肆资。
如下代碼所示:
public BinderServiceProxy(IBinder binder){
if(binder.queryLocalInterface(DESCRIPITON) == null ){
remote = binder;
try {
binder.linkToDeath(new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.i("shajia"," binder server is deaded.");
}
},0);
} catch (RemoteException e) {
e.printStackTrace();
}
}
else
throw new RuntimeException(" this is not a BpBinder.");
}
可以在死亡通知處理中做一些資源回收的操作矗愧,或者再次重啟服務(wù)等操作。
到現(xiàn)在為止郑原,已經(jīng)對(duì)binder的表象有了一個(gè)大概的了解了唉韭,注意只是表象。