Android Review - Binder機制(二)

前一篇我們從理論上來了解Android的Binder機制,本篇從實戰來深入了解Android的Binder機制。

Binder的使用

Binder依賴于Service,在組件(Activity)中通過bindService(),就可以獲取Service中的Binder對象,實現Service與Activity的通信。

服務分為本地服務和遠程服務,都可以使用Binder。

一:本地服務
在本地服務中使用Binder,只需要兩步:

  1. 聲明一個Service,重寫onBind(),返回一個繼承Binder的自定義對象;
  2. 聲明一個ServiceConnection,重寫onServiceConnected(),獲取IBinder對象,然后調用Activity的bindService();

聲明Service:

class ServiceWithBinder : Service() {

    private val TAG = "ServiceWithBinder"

    class InnerBinder : Binder() {

        @Throws(RemoteException::class)
        fun multiply(x: Int, y: Int): Int {
            return x * y
        }

        @Throws(RemoteException::class)
        fun divide(x: Int, y: Int): Int {
            return x / y
        }
    }

    override fun onBind(t: Intent): IBinder? {
        Log.e(TAG, "onBind")
        return InnerBinder()
    }
}

在Activity中綁定服務:

fun myBindService() {
   val mServiceConnBinder = object : ServiceConnection {
        override fun onServiceDisconnected(name: ComponentName) {
            Log.e(TAG, "onServiceDisconnected Binder")
            binderInterface = null
        }

        override fun onServiceConnected(name: ComponentName, service: IBinder) {
            Log.e(TAG, "onServiceConnected Binder")
            binderInterface = service as ServiceWithBinder.InnerBinder
            //調用服務的方法
            binderInterface?.multiply(3,2)
        }
    }
    val intentBinder = Intent(this, ServiceWithBinder::class.java)
    //此時使用bindService開啟服務
   bindService(intentBinder, mServiceConnBinder, Context.BIND_AUTO_CREATE)
    //銷毀服務
   unbindService(mServiceConnBinder)
}

二:遠程服務
在遠程服務中使用Binder,需要三步:創建aidl、聲明Service、調用bindService。

1.創建aidl
aidl是進程間通信接口,需要在客戶端(聲明Service的應用)和服務端(調用Service的應用)同時聲明,并且要完全一致。

首先,在服務端端創建aidl:選中項目目錄->右鍵選中new->然后選中AIDL->填寫文件名(比如:ICalcAIDL)->修改aidl的代碼。

// ICalcAIDL.aidl
package com.pmm.demo.advanced;

// Declare any non-default types here with import statements
interface ICalcAIDL {
    int add(int x , int y);
    int min(int x , int y );
}

然后,在客戶端創建aidl,步驟同上。客戶端的aidl要與服務端的一模一樣,包括包名、類名、方法。

然后,選中Build->Make Project,編譯整個工程,就會在build/generated/source/aidl/debug目錄下生產一個名為ICalcAIDL的接口。

2.聲明Service
在服務端,聲明一個類(比如:MyStub)繼承ICalcAIDL.Stub(ICalcAIDL的代理類,系統幫我們生成的),然后聲明一個繼承Service的類,在onBind()中返回MyStub對象。

class ServiceWithAIDL : Service() {

    private val TAG = "ServiceWithAIDL"

    private val mBinder = object : ICalcAIDL.Stub() {

        @Throws(RemoteException::class)
        override fun add(x: Int, y: Int): Int {
            return x + y
        }

        @Throws(RemoteException::class)
        override fun min(x: Int, y: Int): Int {
            return x - y
        }
    }

    override fun onBind(t: Intent): IBinder? {
        Log.e(TAG, "onBind")
        return mBinder
    }
}

3.調用bindService
在客戶端,聲明一個匿名內部類ServiceConnection,重寫onServiceConnected(),通過ICalcAIDL.Stub.asInterface(iBinder)獲取服務端的ICalcAIDL對象。

fun myBindService() {
   val mServiceConnAIDL = object : ServiceConnection {
        override fun onServiceDisconnected(name: ComponentName) {
            Log.e(TAG, "onServiceDisconnected AIDL")
            mCalcAidl = null
        }

        override fun onServiceConnected(name: ComponentName, service: IBinder) {
            Log.e(TAG, "onServiceConnected AIDL")
            mCalcAidl = ICalcAIDL.Stub.asInterface(service)
           //調用服務的方法
            mCalcAidl?.add(3, 2)
        }
    }
    val intentBinder = Intent(this, ServiceWithAIDL::class.java)
    //此時使用bindService開啟服務
   bindService(intentBinder, mServiceConnAIDL, Context.BIND_AUTO_CREATE)
    //銷毀服務
    unbindService(mServiceConnAIDL)
}

AIDL機制

aidl是進程間通信接口,它不是Java的類,更像一種協議。它的作用是讓AS自動生成Binder類,供客戶端調用。

aidl文件編譯后,會生成一個Java接口,分為3層:ICalcAIDL、Stub、Proxy。

package com.pmm.demo.advanced;

public interface ICalcAIDL extends android.os.IInterface {
   //具體的Binder類
    public static abstract class Stub extends android.os.Binder implements com.pmm.demo.advanced.ICalcAIDL {
        ·······
           //代理類
        private static class Proxy implements com.pmm.demo.advanced.ICalcAIDL {
            ·······
        }
    }

    //加
    public int add(int x, int y) throws android.os.RemoteException;
    //減
    public int min(int x, int y) throws android.os.RemoteException;
}

下面是生成類ICalcAIDL的結構圖

ICalcAIDL的結構圖

1.ICalcAIDL
ICalcAIDL就是aidl文件中聲明的接口,也就是我們要給客戶端調用的功能。

2.Sub
Stub是一個使用ICalcAIDL裝飾的Binder類,是我們實際傳遞給客戶端的對象。通過Stub,客戶端就可以調用ICalcAIDL的方法。

首先是DESCRIPTOR,默認值是包名+類名,作用是在IBinder中查找ICalcAIDL接口。

private static final java.lang.String DESCRIPTOR = "com.pmm.demo.advanced.ICalcAIDL";

然后是asInterface(),作用是返回Proxy對象,也就是把ICalcAIDL對象返回給客戶端。我們注意到一般obj都是對應服務的指引,然后傳遞給Proxy對象。

public static com.pmm.demo.advanced.ICalcAIDL asInterface(android.os.IBinder obj) {
    if ((obj == null)) {
        return null;
    }
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin != null) && (iin instanceof com.pmm.demo.advanced.ICalcAIDL))) {
        return ((com.pmm.demo.advanced.ICalcAIDL) iin);
    }
    //返回Proxy對象
    return new com.pmm.demo.advanced.ICalcAIDL.Stub.Proxy(obj);
}

然后是onTransact(),作用是處理客戶端的請求,并將處理結果返回給客戶端。

@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) {
        //用來指定DESCRIPTOR,從而確定需要通訊的接口
        case INTERFACE_TRANSACTION: {
            reply.writeString(descriptor);
            return true;
        }
        //用來處理ICalcAIDL的add()
        case TRANSACTION_add: {
            //說明data正準備給指定DESCRIPTOR的接口使用
            data.enforceInterface(descriptor);
            int _arg0;
            _arg0 = data.readInt();
            int _arg1;
            _arg1 = data.readInt();
            int _result = this.add(_arg0, _arg1);
            //說明當前操作沒有出現異常
            reply.writeNoException();
            reply.writeInt(_result);
            return true;
        }
         //用來處理ICalcAIDL的min()
        case TRANSACTION_min: {
            data.enforceInterface(descriptor);
            int _arg0;
            _arg0 = data.readInt();
            int _arg1;
            _arg1 = data.readInt();
            int _result = this.min(_arg0, _arg1);
            reply.writeNoException();
            reply.writeInt(_result);
            return true;
        }
        default: {
            return super.onTransact(code, data, reply, flags);
        }
    }
}

3.Proxy
Proxy,顧名思義,就是ICalcAIDL的代理類,用來實現ICalcAIDL的方法。Proxy的作用是將需要傳遞的參數轉化為Parcel,從而跨進程傳輸。

我們來看下核心代碼,只顯示add(),不顯示min方法。

private static class Proxy implements com.pmm.demo.advanced.ICalcAIDL {
    private android.os.IBinder mRemote;

    Proxy(android.os.IBinder remote) {
        mRemote = remote;
    }

    @Override
    public int add(int x, int y) throws android.os.RemoteException {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        int _result;
        try {
            //用來標識_data是給含有DESCRIPTOR標志的Binder接口的參數
            _data.writeInterfaceToken(DESCRIPTOR);
            _data.writeInt(x);
            _data.writeInt(y);
            //調用Stub的transact,處理add請求
            mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
            _reply.readException();
            //獲取Stub的處理結果
            _result = _reply.readInt();
        } finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }
}

我們可以看到,前面asInterface(obj)傳遞進來的對象,是我們代理里真正的執行對象。比如:mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);

AIDL其實通過我們寫的aidl文件,幫助我們生成了一個接口,一個Stub類用于服務端,一個Proxy類用于客戶端調用。

客戶端使用AIDL接口的asInterface()獲取對應的代理,代理調用對應的方法,方法里都有mRemote.transact()進行具體的調用發消息給服務器,最后服務端對應Binder對象中的onTransact()來處理客戶端的請求,并將處理結果返回給客戶端。

PS:本文整理自以下博客
安卓移動架構07-Binder核心機
Android aidl Binder框架淺析

若有發現問題請致郵 caoyanglee92@gmail.com

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