多進程之進程間通信---aidl原理回顧分析整理

概述

最近在學(xué)習(xí)Replugin源碼時,遇到了其中的多進程部分胧谈。由于太久沒使用忆肾,有點生疏,剛好重拾總結(jié)下菱肖。
AIDL是一個縮寫客冈,全稱是Android Interface Definition Language,也就是Android接口定義語言稳强。設(shè)計這門語言的目的是為了實現(xiàn)進程間通信场仲。當然對于上層間應(yīng)用想實現(xiàn)簡單的進程間通信還可以使用Message,當然底層還是通過aidl實現(xiàn)键袱,但操作起來會簡單挺多燎窘,參見 Android中的Service:Binder,Messenger蹄咖,AIDL(2)

源碼分析

編寫如下的BookManager.aidl文件

interface BookManager  {
 //所有的返回值前都不需要加任何東西褐健,不管是什么數(shù)據(jù)類型
// 帶有特定parcelable的情況會涉及到tag及序列化和反序列化,但總的原理一樣澜汤,本文只是為了通過源碼復(fù)習(xí)原理蚜迅,故簡單處理
    String getName();
    void setName(String name);
}

gradle編譯時通過compileXXXXAidltask后會在工程build\generated\source\aidl\{FlavorName}\{BuildType}\{package}下生成對一個BookManager.java文件如下:

public interface BookManager extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.liyuange.liyuange.BookManager
{
private static final java.lang.String DESCRIPTOR = "com.liyuange.liyuange.BookManager";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
 * Cast an IBinder object into an com.liyuange.liyuange.BookManager interface,
 * generating a proxy if needed.
 */
public static com.liyuange.liyuange.BookManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.liyuange.liyuange.BookManager))) {
return ((com.liyuange.liyuange.BookManager)iin);
}
return new com.liyuange.liyuange.BookManager.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_getName:
{
data.enforceInterface(DESCRIPTOR);
java.lang.String _result = this.getName();
reply.writeNoException();
reply.writeString(_result);
return true;
}
case TRANSACTION_setName:
{
data.enforceInterface(DESCRIPTOR);
java.lang.String _arg0;
_arg0 = data.readString();
this.setName(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.liyuange.liyuange.BookManager
{
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;
}
//所有的返回值前都不需要加任何東西,不管是什么數(shù)據(jù)類型

@Override public java.lang.String getName() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);
_reply.readException();
_result = _reply.readString();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public void setName(java.lang.String name) 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.writeString(name);
mRemote.transact(Stub.TRANSACTION_setName, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_setName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
//所有的返回值前都不需要加任何東西俊抵,不管是什么數(shù)據(jù)類型

public java.lang.String getName() throws android.os.RemoteException;
public void setName(java.lang.String name) throws android.os.RemoteException;
}

生成了很多模板性代碼谁不,其實真正實行多進程通信的就是這個生成的java文件。就算我們不寫AIDL文件徽诲,直接按照它生成的 .java 文件那樣寫一個 .java 文件出來刹帕,在服務(wù)端和客戶端中也可以照常使用這個 .java 類來進行跨進程通信吵血,所以其實aidl可以理解為,只是幫android開發(fā)者節(jié)省了寫模板性代碼而已偷溺。

接下來我們分別從服務(wù)端和客戶端看下是怎么利用這個文件進行通信的蹋辅。
服務(wù)端 即將 生成的IBinder通過onbind返回給客戶端

public class AIDLService extends Service {

    //由AIDL文件生成的BookManager
    private final BookManager.Stub mBookManager = new BookManager.Stub() {
       @Override
        public String getName() throws RemoteException {//aidl所定義的接口方法
            return null;
        }

        @Override
        public void setName(String name) throws RemoteException {//aidl所定義的接口方法
          ...
        }
     
    };

  
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
             return mBookManager.asBinder();
    }
}

客戶端 即將返回的binder 調(diào)用asInterface() 方法,獲得我們自定義的接口,然后調(diào)用對應(yīng)方法挫掏。這樣即可實行進程間通信

public class AIDLActivity extends AppCompatActivity {

    //由AIDL文件生成的Java類
    private BookManager mBookManager = null;

    //標志當前與服務(wù)端連接狀況的布爾值侦另,false為未連接,true為連接中
    private boolean mBound = false;

  

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_aidl);
    }

      /**
     * 嘗試與服務(wù)端建立連接
     */
    private void attemptToBindService() {
        Intent intent = new Intent();
        intent.setAction("XXX");//androidmenifest中設(shè)置的service的action
        intent.setPackage("XXXX");//androidmenifest中設(shè)置的service的服務(wù)端package
        bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (!mBound) {
            attemptToBindService();
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (mBound) {
            unbindService(mServiceConnection);
            mBound = false;
        }
    }

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBookManager = BookManager.Stub.asInterface(service);
            mBound = true;

            if (mBookManager != null) {
                try {
                    String name= mBookManager.getName();
                 Log.e(getLocalClassName(), "get book name for remote server, name = "+ name);
                   } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
                mBound = false;
        }
    };
}

重點從生成的代碼中看下asInterface方法如下

public static com.liyuange.liyuange.BookManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.liyuange.liyuange.BookManager))) {
return ((com.liyuange.liyuange.BookManager)iin);
}
return new com.liyuange.liyuange.BookManager.Stub.Proxy(obj);
}

BookManager.Stub尉共,根據(jù)生成的代碼可看出集成了Binder同時實現(xiàn)了我們自定義的接口public static abstract class Stub extends android.os.Binder implements com.liyuange.liyuange.BookManager褒傅,當服務(wù)端與客戶端同在一個進程時iin!=null即 返回的是服務(wù)端創(chuàng)建的BookManager.Stub本身,但我們這種情況是在2個進程袄友,故返回的是com.liyuange.liyuange.BookManager.Stub.Proxy.根據(jù)生成的代碼,該類代碼如下

private static class Proxy implements com.liyuange.liyuange.BookManager//實現(xiàn)我們自定義接口
{
private android.os.IBinder mRemote; //我們傳入的ibinder
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
//所有的返回值前都不需要加任何東西殿托,不管是什么數(shù)據(jù)類型

@Override public java.lang.String getName() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
 mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0);
_reply.readException();
// 如果需要讀取,服務(wù)端返回數(shù)據(jù)杠河,從_reply中取出服務(wù)端執(zhí)行方法的結(jié)果
_result = _reply.readString();
}
finally {
_reply.recycle();
_data.recycle();
}
//需要返回結(jié)果則將結(jié)果返回
return _result;
}
@Override public void setName(java.lang.String name) throws android.os.RemoteException
{
//很容易可以分析出來碌尔,_data用來存儲流向服務(wù)端的數(shù)據(jù)流浇辜,
  //_reply用來存儲服務(wù)端流回客戶端的數(shù)據(jù)流
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
//如果需要傳參參流向服務(wù)端券敌,則寫入
_data.writeString(name);
//調(diào)用 transact() 方法將方法id和兩個 Parcel 容器傳過去
mRemote.transact(Stub.TRANSACTION_setName, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_setName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}

大概流程大同小異,注釋打在了代碼上做分析柳洋。大致做的事就是待诅,因為在不同進程不能直接讀取對方內(nèi)存對象,所以需要進行序列化和反序列化(通過Parcelable實現(xiàn))熊镣,通過binder進行傳遞通信卑雁。

  • 關(guān)于 _data 與 _reply 對象:一般來說,我們會將方法的傳參的數(shù)據(jù)存入_data 中绪囱,而將方法的返回值的數(shù)據(jù)存入 _reply 中——在沒涉及定向 tag 的情況下测蹲。如果涉及了定向 tag ,情況將會變得稍微復(fù)雜些鬼吵,具體是怎么回事請參見這篇博文:你真的理解AIDL中的in扣甲,out,inout么齿椅?

  • 關(guān)于 Parcel :簡單的來說琉挖,Parcel 是一個用來存放和讀取數(shù)據(jù)的容器。我們可以用它來進行客戶端和服務(wù)端之間的數(shù)據(jù)傳輸涣脚,當然示辈,它能傳輸?shù)闹荒苁强尚蛄谢臄?shù)據(jù)。具體 Parcel 的使用方法和相關(guān)原理可以參見這篇文章:Android中Parcel的分析以及使用

  • 關(guān)于 transact() 方法:這是客戶端和服務(wù)端通信的核心方法遣蚀。調(diào)用這個方法之后矾麻,客戶端將會掛起當前線程纱耻,等候服務(wù)端執(zhí)行完相關(guān)任務(wù)后通知并接收返回的 _reply 數(shù)據(jù)流。關(guān)于這個方法的傳參险耀,這里有兩點需要說明的地方:

    • 方法 ID :transact() 方法的第一個參數(shù)是一個方法 ID 膝迎,這個是客戶端與服務(wù)端約定好的給方法的編碼,彼此一一對應(yīng)胰耗。在AIDL文件轉(zhuǎn)化為 .java 文件的時候限次,系統(tǒng)將會自動給AIDL文件里面的每一個方法自動分配一個方法 ID。本例子中生成ID如下
static final int TRANSACTION_getName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_setName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
*   第四個參數(shù):transact() 方法的第四個參數(shù)是一個 int 值柴灯,它的作用是設(shè)置進行 IPC 的模式卖漫,為 0 表示數(shù)據(jù)可以雙向流通,即 _reply 流可以正常的攜帶數(shù)據(jù)回來赠群,如果為 1 的話那么數(shù)據(jù)將只能單向流通羊始,從服務(wù)端回來的 _reply 流將不攜帶任何數(shù)據(jù)。
    注:AIDL生成的 .java 文件的這個參數(shù)均為 0查描。

接下來看下客戶端是如何響應(yīng)的突委?調(diào)用 mRemote.transact后,服務(wù)端就會調(diào)用onTransact進行相應(yīng)處理冬三。通過生成的代碼匀油,查看其源碼如下:

@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_getName:
{
data.enforceInterface(DESCRIPTOR);
java.lang.String _result = this.getName();
reply.writeNoException();
//將方法執(zhí)行的結(jié)果寫入 reply 
reply.writeString(_result);
return true;
}
case TRANSACTION_setName:
{
data.enforceInterface(DESCRIPTOR);
java.lang.String _arg0;
_arg0 = data.readString();
//調(diào)用服務(wù)端中`BookManager.Stub`本身setName方法, 即我們服務(wù)端service中`onbind`方法返回的的`BookManager.Stub`子類方法。
this.setName(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}

直接通過客戶端調(diào)用的方法傳入ID 進行 switch 判斷是掉用戶服務(wù)端哪個方法勾笆,然后調(diào)用我們服務(wù)端service中onbind方法返回的的BookManager.Stub子類方法敌蚜。

我們沒有看到關(guān)于將 reply 回傳到客戶端的相關(guān)代碼,也沒有看到它將相關(guān)參數(shù)傳向服務(wù)端的相關(guān)代碼——它只是把這些參數(shù)都傳入了一個方法窝爪,其中過程同樣是對我們隱藏的——服務(wù)端也同樣弛车,在執(zhí)行完 return true 之后系統(tǒng)將會把 reply 流傳回客戶端,具體是怎么做的就在于IBinder的源碼了蒲每。如果想深入研究IBinder機制纷跛,可參考老羅及其它大神對該機制更底層的分析。
通過隱藏了這些細節(jié)邀杏,我們在 transact() 與 onTransact() 之間的調(diào)用以及數(shù)據(jù)傳送看起來就像是發(fā)生在同一個進程甚至同一個類里面一樣贫奠。我們的操作就像是在一條直線上面走,根本感受不出來其中原來有過曲折——也許這套機制在設(shè)計之初淮阐,就是為了達到這樣的目的叮阅。

總結(jié)

一圖以辟之


2491431-425990373820aaf6.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市泣特,隨后出現(xiàn)的幾起案子浩姥,更是在濱河造成了極大的恐慌,老刑警劉巖状您,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件勒叠,死亡現(xiàn)場離奇詭異兜挨,居然都是意外死亡,警方通過查閱死者的電腦和手機眯分,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門拌汇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人弊决,你說我怎么就攤上這事噪舀。” “怎么了飘诗?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵与倡,是天一觀的道長。 經(jīng)常有香客問我昆稿,道長纺座,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任溉潭,我火速辦了婚禮净响,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘喳瓣。我一直安慰自己馋贤,他們只是感情好,可當我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布夫椭。 她就那樣靜靜地躺著掸掸,像睡著了一般氯庆。 火紅的嫁衣襯著肌膚如雪蹭秋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天堤撵,我揣著相機與錄音仁讨,去河邊找鬼。 笑死实昨,一個胖子當著我的面吹牛洞豁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播荒给,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼丈挟,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了志电?” 一聲冷哼從身側(cè)響起曙咽,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎挑辆,沒想到半個月后例朱,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體孝情,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年洒嗤,在試婚紗的時候發(fā)現(xiàn)自己被綠了箫荡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡渔隶,死狀恐怖羔挡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情间唉,我是刑警寧澤婉弹,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站终吼,受9級特大地震影響镀赌,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜际跪,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一商佛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧姆打,春花似錦良姆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至闲延,卻和暖如春痊剖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背垒玲。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工陆馁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人合愈。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓叮贩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親佛析。 傳聞我的和親對象是個殘疾皇子益老,可洞房花燭夜當晚...
    茶點故事閱讀 43,627評論 2 350

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