跨進程通信原理解析

Binder架構(gòu)的組成

Binder框架有3個方面組成:Binder服務端娶耍、Binder驅(qū)動以及客戶端組成想鹰。

Binder服務端:Binder服務端實際上就是一個Binder對象肢础,該對象一旦創(chuàng)建就會開啟一個隱藏的線程慨蛙,該線程用來接收Binder驅(qū)動發(fā)送的消息,然后執(zhí)行onTransact函數(shù),并根據(jù)onTransact的參數(shù)執(zhí)行不同的服務代碼;因此要實現(xiàn)一個Binder服務就得重載onTransact方法蚀瘸。

重載onTransact方法的主要內(nèi)容就是onTransact函數(shù)的參數(shù)轉(zhuǎn)為服務函數(shù)的參數(shù)奏瞬,而onTransact的參數(shù)來自客戶端 調(diào)用的transact方法;因此酬诀,如果transact參數(shù)確定了,那么onTransact的參數(shù)也就確定了。

Binder驅(qū)動:任意一個服務端Binder對象被創(chuàng)建蒸辆,同時會在Binder驅(qū)動中創(chuàng)建一個 mRemote對象酸些,該對象的類型也是一個Binder類逢渔;客戶端就是通過mRemote 來訪問遠程服務。

客戶端:客戶端想要訪問遠程服務,必須要獲取遠程服務在Binder對象中對應的mRemote引用卒密。怎么獲取呢哲身?

獲取到mRemote后就可以調(diào)用transact方法了,在Binder驅(qū)動中,也重載了transact方法,重載的內(nèi)容主要包括下面幾項:

1.以線程間通信的模式,向服務端發(fā)送客戶端傳遞過來的參數(shù)
2.掛起當前線程,當前線程正是客戶端線程裕膀,并等待服務端執(zhí)行完 指定的 服務函數(shù)后通知(notify)
3.接收到服務端線程的通知扰法,然后繼續(xù)執(zhí)行客戶端線程,并返回到客戶端代碼區(qū).

Binder的結(jié)構(gòu)圖如下:

binder結(jié)構(gòu)圖

2.如何設(shè)計Binder

2.1 設(shè)計Binder服務端

從代碼角度來說很簡單,就是繼承自Binder然后重寫onTransact方法放接,以下為IMediaPlayerService的例子:

public class IMediaPlayerService extends Binder {
    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        return super.onTransact(code, data, reply, flags);
    }

    public void start(String path){

    }

    public void stop(){

    }
}

當要啟動該服務,只需要在Activity中new一個IMediaPlayerService對象即可宗兼。重寫onTransact方法并從data中獲取客戶端傳遞過來的參數(shù),比如start方法中傳遞過來的path。然而结执,這里有個問題,就是服務端如何確定客戶端傳遞過來path在data中的位置?因此蒙具,這里需要和客戶端約定好。

這里假設(shè)客戶端在傳入包裹data中放入的第一個數(shù)據(jù)就是path浪箭,那么onTransact的代碼可以如下寫:

protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
    switch (code){
        case 1001:
            data.enforceInterface("IMediaPlayerService");
            String path = data.readString();
            start(path);
            reply.writeString("我是執(zhí)行完返回的結(jié)果");
            break;
        case 1002:
            stop();
            break;
    }

    return super.onTransact(code, data, reply, flags);
}


onTransact中的code表示 客戶端希望調(diào)用服務端的哪個函數(shù),所以,客戶端和服務端要約定好 一組int值叉存,不同的值表示想要調(diào)用不同的服務端函數(shù)。例如這里的1001表示start痰洒,1002表示要調(diào)用stop。

enforceInterface:是某種校驗,和客戶端的writeInterfaceToken是對應的哩至,等下做具體說明。

readString:表示從包裹data中取出一個字符串path供start調(diào)用冈敛。

如果想要返回客戶端執(zhí)行的結(jié)果就可以在reply中調(diào)用Parcel提供的 相關(guān)函數(shù)來寫入相應的結(jié)果荆陆,比如上面的reply.writeString(“我是執(zhí)行完返回的結(jié)果”)命浴。

2.2 Binder客戶端設(shè)計

想要使用服務端,就得獲得Binder驅(qū)動中對應的mRemote的引用。獲取方法下面詳解失暂,然后調(diào)用mRemote的transact方法。transact方法原型如下:

public boolean transact(int code, @NonNull Parcel data,@Nullable Parcel reply, int flags)throws RemoteException;

其中data表示要傳遞給服務端的包裹(Parcel)笙瑟,遠程服務端需要的數(shù)據(jù)都需要放入這個包裹中戒突。包裹只支持原子類型:String今艺、int、long等题涨,以及實現(xiàn)Parcelable接口的對象向挖∷馕#客戶端調(diào)用的代碼可以寫成類似下面這樣的:

IBinder mRemote = null;
String path = "/sdcard/media/xxx.mp4";
int code = 1001;
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken("IMediaPlayerService"); //和服務端enforceInterfac一一對應
data.writeString(path);
mRemote.transact(code, data, reply, 0);
IBinder binder = reply.readStrongBinder();
reply.recycle();
data.recycle();

看到上面的代碼感覺是不是很熟悉,是不是和使用aidl進行IPC中自動生成的代碼很像蛔翅,哈哈。上面分析可知洁灵,data和reply不是new出來的牍汹,而是調(diào)用Parcel.obtain()申請的有决。就和郵局一樣痒芝,你只能使用郵局使用的信封俄精。其中date和reply都是由客戶端提供的康二,data提供服務端需要的數(shù)據(jù)味混,reply是給服務端將返回結(jié)果放入其中的。

writeInterfaceToken:標注遠程服務的名稱拷获,理論上不是必須的,因為客戶端已經(jīng)獲取了遠程服務的mRemote引用,那么就不會調(diào)用了其他的遠程服務九昧。該名稱是Binder驅(qū)動確保客戶端想調(diào)用的是指定的服務端悲敷。

writeString:用于向包裹中寫入一條String類型的數(shù)據(jù)部宿。注意,包裹中添加的內(nèi)容是有序的瓢湃,這個順序必須是客戶端和服務端之前約定好的理张。在服務端的onTransact方法中會按照指定的順序取出數(shù)據(jù)。

最后調(diào)用transact方法:調(diào)用該方法后箱季,客戶端線程進入 Binder驅(qū)動涯穷,Binder驅(qū)動會掛起當前的線程,并向遠程服務中發(fā)送一個消息藏雏,該消息包含客戶端傳進來的包裹拷况,服務端拿到包裹后作煌,進行數(shù)據(jù)解析,然后調(diào)用相應的服務函數(shù)赚瘦,最后將返回結(jié)果寫入reply中粟誓。然后向Binder驅(qū)動發(fā)送一個通知(notify)喚醒客戶端線程,從而使得客戶端線程從Binder驅(qū)動代碼區(qū)返回到客戶端代碼區(qū)起意。

tansact方法中最后一個參數(shù)flag表示IPC的調(diào)用模式鹰服,0表示服務端執(zhí)行完后會返回執(zhí)行結(jié)果,1表示單向的揽咕,服務端不會返回執(zhí)行結(jié)果悲酷。

3.如何獲取Binder對象

使用過AIDL技術(shù)的同學應該都能想到,那就是使用Service亲善。調(diào)用bindService即可设易,bindService函數(shù)原型如下:

public boolean bindService(Intent service, ServiceConnection conn,int flags);

最關(guān)鍵的就是其中的ServiceConnection 蛹头,ServiceConnection 中包含這個函數(shù):

void onServiceConnected(ComponentName name, IBinder service);

請注意onServiceConnected第二個參數(shù)Service顿肺,當客戶端調(diào)用AMS啟動某個Service后,如果Service正常啟動渣蜗,那么AMS就會調(diào)用ActivityThread中的ApplicationThread對象屠尊,調(diào)用參數(shù)中就包含Binder對象的引用,然后在 ApplicationThread中會回調(diào)bindService中的conn接口耕拷。因此讼昆,客戶端就可以在onServiceConnected方法中將service參數(shù)保存為一個全局變量,以供隨時調(diào)用斑胜。這就解決了第一個問題控淡,客戶端如何獲得Binder對象的引用嫌吠。

4.保證包裹類的參數(shù)順序

Android SDK中提供了aidl工具止潘,該工具可以把一個aidl文件轉(zhuǎn)換為一個java文件,在該Java類文件中辫诅,同時重載了onTransact和transact方法凭戴,統(tǒng)一了存入包裹和讀取包裹的參數(shù)。Aidl工具不是必須的炕矮,有經(jīng)驗的程序員完全可以自己寫出參數(shù)統(tǒng)一的包裹存入和包裹讀出的代碼么夫。

下面我們看看aidl文件自動生成的java文件是什么樣的?我們先定義一個aidl文件肤视,如下:

interface IBookManager {
    List<Book> getAllBooks();
    void addBook(in Book book);
}

注意档痪,aidl文件只支持原子類型和實現(xiàn)了Parcelable接口的類。上面的Book類就實現(xiàn)了Parcelable邢滑。對應的java文件如下:

package com.example.za_zhujiangtao.zhupro;
// Declare any non-default types here with import statements

public interface IBookManager extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.example.za_zhujiangtao.zhupro.IBookManager
{
private static final java.lang.String DESCRIPTOR = "com.example.za_zhujiangtao.zhupro.IBookManager";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
 * Cast an IBinder object into an com.example.za_zhujiangtao.zhupro.IBookManager interface,
 * generating a proxy if needed.
 */
public static com.example.za_zhujiangtao.zhupro.IBookManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.za_zhujiangtao.zhupro.IBookManager))) {
return ((com.example.za_zhujiangtao.zhupro.IBookManager)iin);
}
return new com.example.za_zhujiangtao.zhupro.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
{
java.lang.String descriptor = DESCRIPTOR;
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
case TRANSACTION_getAllBooks:
{
data.enforceInterface(descriptor);
java.util.List<com.example.za_zhujiangtao.zhupro.Book> _result = this.getAllBooks();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook:
{
data.enforceInterface(descriptor);
com.example.za_zhujiangtao.zhupro.Book _arg0;
if ((0!=data.readInt())) {
_arg0 = com.example.za_zhujiangtao.zhupro.Book.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.example.za_zhujiangtao.zhupro.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.example.za_zhujiangtao.zhupro.Book> getAllBooks() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.example.za_zhujiangtao.zhupro.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getAllBooks, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.example.za_zhujiangtao.zhupro.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public void addBook(com.example.za_zhujiangtao.zhupro.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();
}
}
}
static final int TRANSACTION_getAllBooks = (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.example.za_zhujiangtao.zhupro.Book> getAllBooks() throws android.os.RemoteException;
public void addBook(com.example.za_zhujiangtao.zhupro.Book book) throws android.os.RemoteException;
}

這些代碼主要完成了一下的3個任務:

1.定義了一個interface IBookManager腐螟,內(nèi)部包含aidl文件聲明的所有方法,并且繼承了IInterface,即該interface的實現(xiàn)類需要提供一個asBinder()函數(shù)乐纸。

2.定義一個Proxy類衬廷,該類實現(xiàn)了IBookManager,該類作為客戶端訪問服務端的代理汽绢,所謂代理就是為了前面提到的第二個問題—統(tǒng)一包裹的輸入和讀取參數(shù)吗跋。

3.定義一個Sub類,他是一個抽象類宁昭,繼承了Binder類且實現(xiàn)了IBookManager接口跌宛。之所以是抽象類是因為具體的服務函數(shù)需要程序員自己在Service類中實現(xiàn)。例如上面的onTransact方法中的 addBook方法最終調(diào)用的是程序員自己在Service類中實現(xiàn)的积仗。

private Binder mBinder = new IBookManager.Stub() {
    @Override
    public List<Book> getAllBooks() throws RemoteException {
        return mBookList;
    }

    @Override
    public void addBook(Book book) throws RemoteException {
        mBookList.add(book);
    }

};

這個就是我在Service類中實現(xiàn)的秩冈。同時,在Sub類中重載了onTransact方法斥扛,由于transact方法內(nèi)部給包裹類寫入順序是由aidl工具決定的入问,因此,在onTransact方法中稀颁,aidl工具自然知道按照何種順序從包裹中取出數(shù)據(jù)芬失。

在Sub類中還定義了一些int型參數(shù),如TRANSACTION_getAllBooks匾灶, TRANSACTION_addBook棱烂, 這些常量與服務函數(shù)對應,onTransact和transact方法的第一個參數(shù)就是code的值就來源于此阶女。

在Sub類中還定義了一個方法颊糜,asInterface:提供這個函數(shù)的原因是服務端提供的服務除了其他進程可以調(diào)用之外,在本服務進程內(nèi)部的其他類也可以調(diào)用秃踩,對于后者則不需要經(jīng)過IPC調(diào)用衬鱼,而直接在進程內(nèi)部調(diào)用。Bindern內(nèi)部有一個queryLocalInterface的方法憔杨,該函數(shù)是通過輸入字符串來判斷來判斷該Binder對象是不是本地Binder對象的引用鸟赫。

總結(jié)下來說就是,當創(chuàng)建一個Binder對象時消别,服務端進程內(nèi)部會創(chuàng)建一個Binder對象抛蚤,Binder驅(qū)動中也會創(chuàng)建一個Binder對象。如果從遠程獲取服務端的Binder寻狂,則只會返回Binder驅(qū)動中的Binder對象岁经。而如果從服務端進程內(nèi)部獲取Binder對象,則會返回服務端本身的Binder對象蛇券。如下圖:

服務端客戶端交互圖

因此缀壤,asInterface函數(shù)正是利用了queryLocalInterface方法朽们,提供了一個統(tǒng)一接口。無論是本地服客戶端還是遠程客戶端诉位,當獲取了Binder對象后骑脱,都可以把該Binder對象作為asInterface的參數(shù),來返回一個IBookManager接口苍糠。
轉(zhuǎn)載自:https://me.csdn.net/zhujiangtaotaise

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末叁丧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子岳瞭,更是在濱河造成了極大的恐慌拥娄,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瞳筏,死亡現(xiàn)場離奇詭異稚瘾,居然都是意外死亡,警方通過查閱死者的電腦和手機姚炕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門摊欠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人柱宦,你說我怎么就攤上這事些椒。” “怎么了掸刊?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵免糕,是天一觀的道長。 經(jīng)常有香客問我忧侧,道長石窑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任蚓炬,我火速辦了婚禮松逊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘试吁。我一直安慰自己棺棵,他們只是感情好,可當我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布熄捍。 她就那樣靜靜地躺著,像睡著了一般母怜。 火紅的嫁衣襯著肌膚如雪余耽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天苹熏,我揣著相機與錄音碟贾,去河邊找鬼币喧。 笑死,一個胖子當著我的面吹牛袱耽,可吹牛的內(nèi)容都是我干的杀餐。 我是一名探鬼主播,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼朱巨,長吁一口氣:“原來是場噩夢啊……” “哼史翘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起冀续,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤琼讽,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后洪唐,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钻蹬,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年凭需,在試婚紗的時候發(fā)現(xiàn)自己被綠了问欠。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡粒蜈,死狀恐怖溅潜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情薪伏,我是刑警寧澤滚澜,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站嫁怀,受9級特大地震影響设捐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜塘淑,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一萝招、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧存捺,春花似錦槐沼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至肖油,卻和暖如春兼吓,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背森枪。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工视搏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留审孽,地道東北人。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓浑娜,卻偏偏與公主長得像佑力,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子筋遭,可洞房花燭夜當晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容