Android IPC機制(三):淺談Binder的使用

一、前言

在上一篇文章Android IPC機制(二):AIDL的基本使用方法中,筆者講述了安卓進程間通訊的一個主要方式,利用AIDL進行通訊,并介紹了AIDL的基本使用方法。其實AIDL方式利用了Binder來進行跨進程通訊,Binder是Android中的一種跨進程通訊方式,其底層實現原理比較復雜,限于筆者水平,不能展開詳談,所以這篇文章主要談談以AIDL為例,談談Binder的使用方法。

二、原理

上一篇文章中創建了一個IMyAidl.aidl文件,即接口文件,隨即編譯了該文件,生成了一個.java文件,該文件在gen目錄下:


IMyAidl.java所在路徑

  打開該文件,得到如下代碼:

* This file is auto-generated.  DO NOT MODIFY. 
 * Original file: G:\\Android\\Project\\MyAidl\\app\\src\\main\\aidl\\com\\chenyu\\service\\IMyAidl.aidl 
 */  
package com.chenyu.service;  
  
public interface IMyAidl extends android.os.IInterface {  
    /** 
     * Local-side IPC implementation stub class. 
     */  
    public static abstract class Stub extends android.os.Binder implements com.chenyu.service.IMyAidl {  
        ......  
  
    public void addPerson(com.chenyu.service.Person person) throws android.os.RemoteException;  
  
    public java.util.List<com.chenyu.service.Person> getPersonList() throws android.os.RemoteException;</span>  
}

其中省略了一部分,我們先從大體上認識,然后在深入。
(1)從大體上看,該java文件是一個接口,繼承了IInterface接口,接著,聲明了一個靜態內部抽象類:Stub,然后是兩個方法,可以看到,這兩個方法分別是原IMyAidl.aidl文件內聲明的兩個方法。
(2)我們看回Stub類,它繼承了Binder,同時實現了IMyAidl。這個類實現了自己的接口!那么可想而知,該接口所聲明的addPerson,getPersonList方法,將會在Stub類得到實現,具體如何實現,我們展開Stub類:

public static abstract class Stub extends android.os.Binder implements com.chenyu.service.IMyAidl {  
        private static final java.lang.String DESCRIPTOR = "com.chenyu.service.IMyAidl";  
  
        /** 
         * Construct the stub at attach it to the interface. 
         */  
        public Stub() {<span style="white-space:pre">     </span>// 1  
            this.attachInterface(this, DESCRIPTOR);  
        }  
  
        /** 
         * Cast an IBinder object into an com.chenyu.service.IMyAidl interface, 
         * generating a proxy if needed. 
         */  
        public static com.chenyu.service.IMyAidl asInterface(android.os.IBinder obj) {    // 2  
            if ((obj == null)) {  
                return null;  
            }  
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);  
            if (((iin != null) && (iin instanceof com.chenyu.service.IMyAidl))) {  
                return ((com.chenyu.service.IMyAidl) iin);  
            }  
            return new com.chenyu.service.IMyAidl.Stub.Proxy(obj);  
        }  
  
        @Override  
        public android.os.IBinder asBinder() {<span style="white-space:pre">      </span>// 3  
            return this;  
        }  
  
        @Override  
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {<span style="white-space:pre">             </span>// 4  
            switch (code) {  
                case INTERFACE_TRANSACTION: {  
                    reply.writeString(DESCRIPTOR);  
                    return true;  
                }  
                case TRANSACTION_addPerson: {  
                    data.enforceInterface(DESCRIPTOR);  
                    com.chenyu.service.Person _arg0;  
                    if ((0 != data.readInt())) {  
                        _arg0 = com.chenyu.service.Person.CREATOR.createFromParcel(data);  
                    } else {  
                        _arg0 = null;  
                    }  
                    this.addPerson(_arg0);  
                    reply.writeNoException();  
                    return true;  
                }  
                case TRANSACTION_getPersonList: {  
                    data.enforceInterface(DESCRIPTOR);  
                    java.util.List<com.chenyu.service.Person> _result = this.getPersonList();  
                    reply.writeNoException();  
                    reply.writeTypedList(_result);  
                    return true;  
                }  
            }  
            return super.onTransact(code, data, reply, flags);  
        }  
  
        private static class Proxy implements com.chenyu.service.IMyAidl {<span style="white-space:pre">  </span>// 5  
            ...  
        }  
  
        static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); // 6  
        static final int TRANSACTION_getPersonList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);  
    } 

(3)從上往下地,我們逐個分析一下各個方法或者變量的作用:
①Stub()構造方法:此方法調用了父類Binder的attachInterface()方法,將當前的Interface與Binder聯系起來,由于傳遞了DESCRIPTOR這個參數,唯一標識了當前Interface。

②asInterface(IBinder obj) :靜態方法,傳遞了一個接口對象,該對象從哪里傳遞進來的呢?我們來看看上一章博客的客戶端代碼:

public void onServiceConnected(ComponentName name, IBinder service) {  
            Log.d("cylog", "onServiceConnected success");  
            iMyAidl=IMyAidl.Stub.asInterface(service);  
  
        }  

在這里,可以看到,調用了IMyAidl.Stub.asInterface(service)方法,即上面的②號方法,并且把service傳遞了進去,我們接著往下看:

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

首先判斷obj是否有效,如果無效直接返回Null,說明客戶端與服務端的連接失敗了。接著調用了obj.queryLocalInterface(DESCRIPTOR)方法,為IInterface的對象賦值,注意到這里再次傳遞了DESCRIPTOR參數,可以猜測,這個方法應該是查找與當前Interface相關的一個方法,我們看看IBinder接口的queryLocalInterface()方法:

/** 
     * Attempt to retrieve a local implementation of an interface 
     * for this Binder object.  If null is returned, you will need 
     * to instantiate a proxy class to marshall calls through 
     * the transact() method. 
     */  
    public IInterface queryLocalInterface(String descriptor);  

大概意思是說,根據descriptor的值,試圖為Binder取回一個本地的interface,其中local意思應為當前進程,如果返回值是null,那么應該實例化一個proxy類。在了解了obj.queryLocalInterface(DESCRIPTOR)方法后,我們再次回到asInterface(obj)方法,繼續往下看:接著是一個if判斷,主要判斷客戶端與服務端是否處于同一進程,如果處于同一進程,那么直接返回了Stub對象本身,如果不是同一個進程,那么就會新建一個Proxy代理類(下面會提到)。

③asBinder():此方法用于返回當前對象本身。

④onTransact(int code,Parcel data,Parcel reply,int flags):該方法一般運行在服務端中的Binder線程池中,即遠程請求會在該方法得到處理。傳遞的code值用于判斷客戶端的請求目標,是addPerson或者是getPersonList。我們以請求目標為addPerson()為例分析一下,提取其主要函數體如下:

case TRANSACTION_addPerson: {  
                    data.enforceInterface(DESCRIPTOR);  
                    com.chenyu.service.Person _arg0;  
                    if ((0 != data.readInt())) {  
                        _arg0 = com.chenyu.service.Person.CREATOR.createFromParcel(data);  
                    } else {  
                        _arg0 = null;  
                    }  
                    this.addPerson(_arg0);  
                    reply.writeNoException();  
                    return true;  
                }

首先聲明了_arg0是Person類的對象,接著,以data為參數調用了Person類的CREATOR.createFromParcel方法反序列化生成了Person類,這也是為什么實現了Parcelable接口的類應該同時實現CREATOR,原來在這里調用了反序列化的方法。接著,調用this.addPerson(_arg0)方法,注意:這里的this代表當前的Binder對象,那么由Binder調用的addPerson(_arg0)方法,實際上是由綁定到Binder的service調用的,即服務端調用了自身的addPerson方法。為了方便明白,讓我們來回顧一下上一篇文章服務端的代碼:

private IBinder iBinder= new IMyAidl.Stub() {  
        @Override  
        public void addPerson(Person person) throws RemoteException {  
            persons.add(person);  
        }  
  
        @Override  
        public List<Person> getPersonList() throws RemoteException {  
            return persons;  
        }  
    };  

是不是一下子就明白了?IMyAidl.Stub()實現的接口,其中的方法在服務端得到了實現:addPerson和getPersonList()。當在Binder線程池中,調用了this.addPerson()方法,實際上回調了服務端的addPerson方法,而底層到底是怎么實現的,限于筆者的水平,暫時不了解,等以后筆者再深入了解Binder的工作機制再回答這個問題。

好了,回到當前的類,我們繼續往下看:
⑤private static class Proxy:這里又出現了一個私有的靜態內部類,關于這個類將在接下來詳細講述。

⑥最后兩行代碼分別是兩個常量,標志了兩個方法,即上面提到的code值。

(4)Proxy類,也實現了IMyAidl接口,同時實現了addPerson和getPersonList的方法。而Proxy類在哪里被實例化的呢?是上面(3)②中,當客戶端與服務端不在同一個進程的時候,就會實例化這個代理類,并返回給客戶端。什么叫做代理類呢?所謂代理,即一個中介,客戶端拿到的實例,能操作服務端的部分功能,讓客戶端以為自己已經拿到了服務端的實例,其實不是,只是拿到服務端的一個代理而已。接下來我們展開該類,看看內部:

private static class Proxy implements com.chenyu.service.IMyAidl {  
            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 void addPerson(com.chenyu.service.Person person) 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 ((person != null)) {  
                        _data.writeInt(1);  
                        person.writeToParcel(_data, 0);  
                    } else {  
                        _data.writeInt(0);  
                    }  
                    mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);  
                    _reply.readException();  
                } finally {  
                    _reply.recycle();  
                    _data.recycle();  
                }  
            }  
  
            @Override  
            public java.util.List<com.chenyu.service.Person> getPersonList() throws android.os.RemoteException {  
                android.os.Parcel _data = android.os.Parcel.obtain();  
                android.os.Parcel _reply = android.os.Parcel.obtain();  
                java.util.List<com.chenyu.service.Person> _result;  
                try {  
                    _data.writeInterfaceToken(DESCRIPTOR);  
                    mRemote.transact(Stub.TRANSACTION_getPersonList, _data, _reply, 0);<span style="white-space:pre"> </span>   //①  
                    _reply.readException();  
                    _result = _reply.createTypedArrayList(com.chenyu.service.Person.CREATOR);  
                } finally {  
                    _reply.recycle();  
                    _data.recycle();  
                }  
                return _result;  
            }  
        }

這里關注兩個接口方法的實現:addPerson和getPersonList,這兩個方法已經多次出現了,而在代理類實現的這兩個方法,是運行在客戶端的!!!其主要實現過程是這樣的:當客戶端拿到代理類,調用addPerson或者getPersonList方法,首先會創建輸入型Parcel對象_data和輸出型Parcel對象_reply,接著調用①號代碼調用transact來發起RPC遠程請求,同時當前線程會被掛起,此時,服務端的onTransact會被調用,即上面所說的(3)④號代碼,當服務端處理完請求后,會返回數據,當前線程繼續執行知道返回 _result結果。

至此,對于IPC的方式之一——AIDL的原理已經剖析完畢,接下來總結一下:

1、客戶端發出綁定請求,服務端返回一個Binder對象,該對象能處理跨進程請求,而客戶端拿到的是Binder對象的引用,Binder的實體是在服務端的。客戶端執行asInterface()方法,如果客戶端和服務端處于同一進程,則直接返回服務端的Stub對象本身,如果處于不同進程,則返回的是Stub.proxy代理類對象。
2、客戶端發送遠程請求(addPerson或者getPersonList),此時客戶端線程掛起,Binder拿到數據后,對數據進行處理如在不同進程,會把數據寫入Parcel,調用Transact方法。
3、觸發onTransact方法,該方法運行在Binder線程池,方法中會調用到服務端實現的接口方法,當數據處理完畢后,返回reply值,經過Binder返回客戶端,此時客戶端線程被喚醒。

三、優化

最后說一說如何優化AIDL,上面提到,客戶端發送請求后,會被掛起,這意味著,如果處理數據的時間過長,那么該線程就一直等不到喚醒,這是很嚴重的,如果在UI線程發送請求,會直接導致ANR,所以我們需要在子線程發送異步請求,這樣才能避免ANR。還有一點,Binder可能是意外死亡的,如果Binder意外死亡,那么子線程可能會一直掛起,所以我們要啟用重新連接服務。有兩個方法,一個是給Binder設置DeathRecipient監聽,另一個是在onServiceDisconnected中重連服務。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,559評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,442評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,581評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,922評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,096評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,639評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,374評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,591評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,789評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,322評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,554評論 2 379

推薦閱讀更多精彩內容