Android IPC機制(四):細說Binder連接池

一、 前言

在上一篇文章Android IPC機制(三):淺談Binder的使用中,筆者講述了Binder的使用及其工作機制,利用AIDL方式能很方便地進行客戶端和服務端的跨進程通信。
  但是,我們想一下,如果按照我們之前的使用方法,必須滿足一個AIDL接口對應一個service,那么問題來了,假如我們的應用,有很多模塊,而每一個模塊都需要和服務端通訊,那么我們也要為每一個模塊創建特定的aidl文件,那么服務端service也會產生很多個,顯然,如果aidl接口變多,那么service也會跟著變多,那么這樣的用戶體驗就會非常不好,那么我們該怎么做呢?在任玉剛著的《Android 開發藝術探索》一書中,給出了一個Binder連接池的概念,即利用一個Binder連接池來管理所有Binder,服務端只需要管理這個Bindere連接池即可,這樣就能實現一個service管理多個Binder,為不同的模塊返回不同的Binder,以實現進程間通訊。所以,本文將講述如何實現Binder連接池。

二、實現

1、先提供兩個AIDL接口來模擬多個模塊都要使用AIDL的情況:
ISpeak接口和ICalculate接口:

package com.chenyu.service;
interface ISpeak {
    void speak();
}
package com.chenyu.service;
interface ICalculate {
    int add(in int num1,in int num2);
}

接著,實現這兩個接口:分別為Speak.java和Calculate.java文件:

public class Speak extends Stub {
    public Speak() {
        Log.d("cylog","我被實例化了..............");
    }
    @Override
    public void speak() throws RemoteException {
        int pid = android.os.Process.myPid();
        Log.d("cylog","當前進程ID為:"+pid+"-----"+"這里收到了客戶端的speak請求");
    }
}
public class Calculate extends ICalculate.Stub {
    @Override
    public int add(int num1, int num2) throws RemoteException {
        int pid = android.os.Process.myPid();
        Log.d("cylog", "當前進程ID為:"+pid+"----"+"這里收到了客戶端的Calculate請求");
        return num1+num2;
    }
}

可以看到,這兩個接口的實現類,都是繼承了Interface.Stub類,這個在上一章的服務端代碼出現過,是在服務端的service內部實現了接口的方法,而這里我們把實現了接口的方法從服務端抽離出來了,其實這個實現類依然是運行在服務端的進程中,從而實現了AIDL接口和服務端的解耦合工作,讓服務端不再直接參與AIDL接口方法的實現工作。
  那么,服務端通過什么橋梁與AIDL接口聯系呢?答案就是Binder連接池。Binder連接池管理著所有的AIDL接口,就如一位將軍統帥著千軍??蛻舳诵枰裁碆inder,就提供信息給Binder連接池,而連接池根據相應信息返回正確的Binder,這樣客戶端就能執行特定的操作了??梢哉f,Binder連接池的思路,非常類似設計模式之中的工廠模式。接下來我們看Binder連接池的具體實現:

2、為Binder連接池創建AIDL接口:IBinderPool.aidl:

interface IBinderPool {
    IBinder queryBinder(int binderCode);  //查找特定Binder的方法
}

為什么需要這個接口?我們從上面的分析可以知道,service端并不直接提供具體的Binder,那么客戶端和服務端連接的時候就應該返回一個IBinderPool對象,讓客戶端拿到這個IBinderPool的實例,然后由客戶端決定應該用哪個Binder。所以服務端的代碼很簡單,只需要返回IBinderPool對象即可:

3、服務端service代碼:

public class BinderPoolService extends Service {

    private Binder mBinderPool = new BinderPool.BinderPoolImpl();   // 1
    private int pid = Process.myPid();
    @Override
    public IBinder onBind(Intent intent) {
        Log.d("cylog", "當前進程ID為:"+pid+"----"+"客戶端與服務端連接成功,服務端返回BinderPool.BinderPoolImpl 對象");
        return mBinderPool;
    }
}

①號代碼處,實例化了一個BinderPool.BinderPoolImpl類,并在onBind方法返回了這個mBinderPool對象。

4、接下來我們看BinderPool的具體實現,代碼比較長,我們先大體上認識,再詳細分析:

public class BinderPool {
    public static final int BINDER_SPEAK = 0;
    public static final int BINDER_CALCULATE = 1;

    private Context mContext;
    private IBinderPool mBinderPool;
    private static volatile BinderPool sInstance;
    private CountDownLatch mConnectBinderPoolCountDownLatch;

    private BinderPool(Context context) {               // 1
        mContext = context.getApplicationContext();
        connectBinderPoolService();
    }

    public static BinderPool getInstance(Context context) {     // 2
        if (sInstance == null) {
            synchronized (BinderPool.class) {
                if (sInstance == null) {
                    sInstance = new BinderPool(context);
                }
            }
        }
        return sInstance;
    }

    private synchronized void connectBinderPoolService() {      // 3
        mConnectBinderPoolCountDownLatch = new CountDownLatch(1);
        Intent service = new Intent(mContext, BinderPoolService.class);
        mContext.bindService(service, mBinderPoolConnection,
                Context.BIND_AUTO_CREATE);
        try {
            mConnectBinderPoolCountDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public IBinder queryBinder(int binderCode) {          // 4
        IBinder binder = null;
        try {
            if (mBinderPool != null) {
                binder = mBinderPool.queryBinder(binderCode);
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        return binder;
    }

    private ServiceConnection mBinderPoolConnection = new ServiceConnection() {   // 5

        @Override
        public void onServiceDisconnected(ComponentName name) {
            
        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBinderPool = IBinderPool.Stub.asInterface(service);
            try {
                mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipient, 0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            mConnectBinderPoolCountDownLatch.countDown();
        }
    };

    private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() {    // 6
        @Override
        public void binderDied() {
            mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0);
            mBinderPool = null;
            connectBinderPoolService();
        }
    };

    public static class BinderPoolImpl extends IBinderPool.Stub {     // 7

        public BinderPoolImpl() {
            super();
        }

        @Override
        public IBinder queryBinder(int binderCode) throws RemoteException {
            IBinder binder = null;
            switch (binderCode) {
                case BINDER_SPEAK: {
                    binder = new Speak();
                    break;
                }
                case BINDER_CALCULATE: {
                    binder = new Calculate();
                    break;
                }
                default:
                    break;
            }

            return binder;
        }
    }

}

大體上看,這個類完成的功能有實現客戶端和服務端的連接,同時內有還有一個靜態內部類:BinderPoolImpl,繼承了IBinderPool.Stub,這也非常眼熟,所以這個靜態內部類應該是運行了服務端的。好了,我們從上往下分析每一個方法的作用:
①private BinderPool(Context context)構造方法:這里傳遞了context對象,注意到,這個構造方法使用了private修飾,那么外界是無法直接調用構造器的,所以有了②號方法。

②public static BinderPool getInstance(Context context):看到getInstance字樣,熟悉設計模式的讀者應該知道了這里是使用了單例模式,而且是線程同步的懶漢式單例模式,在方法內部,把傳遞進來的context上下文參數傳遞進構造函數,即此時調用了①號方法,接著①號方法調用connectBinderPoolService()方法,即③號方法。

③private synchronized void connectBinderPoolService():這個方法主要用于客戶端與服務端建立連接,在方法內部出現了CountDownLatch類,這個類是用于線程同步的,由于bindService()是異步操作,所以如果要確保客戶端在執行其他操作之前已經綁定好服務端,就應該先實現線程同步。
  這里簡單提一下這個類:
  CountDownLatch類有三個主要方法:
   (1)構造方法 CountDownLatch(int num):這里傳遞一個num值,為countdownlatch內部的計時器賦值。
   (2)countdown():每當調用一次這個方法,countdownlatch實例內部計時器數值 - 1。
   (3)await():讓當前線程等待,如果內部計時器變為0,那么喚醒當前線程。

④public IBinder queryBinder(int binderCode):根據具體的binderCode值來獲得某個特定的Binder,并返回。

⑤private ServiceConnection mBinderPoolConnection = new ServiceConnection(){} :這個類似于上一章客戶端的連接代碼,在服務端與客戶端連接成功的時候,會回調當前的onServiceConnected()函數,我們來著重看看這個函數:

@Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBinderPool = IBinderPool.Stub.asInterface(service);
            try {
                mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipient, 0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            mConnectBinderPoolCountDownLatch.countDown();
        }

注意到,方法內部執行了mBinderPool=IBinderPool.Stub.asInterface(service)方法,由上一章的分析可知,這里的mBinderPool實際上是IBinderPool的一個代理對象,即此時客戶端獲得了服務端Binder連接池的一個代理對象。接著,最后執行了mConnectBinderPoolCountDownLatch.countDown()方法,此時,執行bindService()的線程就會被喚醒。

⑥private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient(){}:為IBinder設置死亡監聽,如果連接意外中斷,會自動重新連接。

⑦public static class BinderPoolImpl extends IBinderPool.Stub{} :這個類實現了IBinderPool.Stub,內部實現了IBinderPool的接口方法,這個實現類運行在服務端。內部是queryBinder()方法的實現,根據不同的Bindercode值來實例化不同的實現類,比如Speak或者Calculate,并作為Binder返回給客戶端。當客戶端調用這個方法的時候,實際上已經是進行了一次AIDL方式的跨進程通信。

5、分析完BinderPool代碼,最后,我們實現客戶端代碼:

package com.chenyu.binderpool;
import android.app.Activity;
import android.os.*;
import android.util.Log;

import com.chenyu.service.BinderPool;
import com.chenyu.service.Calculate;
import com.chenyu.service.ICalculate;
import com.chenyu.service.ISpeak;
import com.chenyu.service.Speak;

public class MainActivity extends Activity {

    private ISpeak mSpeak;
    private ICalculate mCalculate;
    private int pid = android.os.Process.myPid();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(new Runnable() {
            @Override
            public void run() {
                startWork();
            }
        }).start();
    }

    private void startWork() {
        Log.d("cylog","當前進程ID為:"+pid);
        Log.d("cylog","獲取BinderPool對象............");
        BinderPool binderPool = BinderPool.getInsance(MainActivity.this);     // 1
        Log.d("cylog","獲取speakBinder對象...........");
        IBinder speakBinder = binderPool.queryBinder(BinderPool.BINDER_SPEAK);  // 2
        Log.d("cylog","獲取speak的代理對象............");
        mSpeak = (ISpeak) ISpeak.Stub.asInterface(speakBinder);    // 3 
        try {
            mSpeak.speak();     // 4
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        Log.d("cylog","獲取calculateBinder對象...........");
        IBinder calculateBinder = binderPool.queryBinder(BinderPool.BINDER_CALCULATE);
        Log.d("cylog","獲取calculate的代理對象............");
        mCalculate = (ICalculate) ICalculate.Stub.asInterface(calculateBinder);
        try {
            Log.d("cylog",""+mCalculate.add(5,6));
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

}

由于跨進程通信是耗時操作,這里利用了子線程來進行綁定以及請求等操作。這里簡單分析一下從子線程開始,整個跨進程通訊流程是怎樣的:
  首先,在①處,調用了BinderPool的getInstance()方法,在里面執行了綁定服務的操作,此時得到的binderPool是BinderPool對象。接著執行②號代碼,調用BinderPool對象的queryBinder()方法,此時發生了AIDL跨進程請求,得到服務端返回的特定的IBinder對象。接著執行③號代碼,調用ISpeak.Stub.asInterface(IBinder iBinder)方法,把剛才獲得的IBinder對象傳遞進去,此時返回了Speak的Proxy代理對象。 最后執行④號代碼,調用代理對象mSpeak的speak()方法,此時再次發生了AIDL跨進程請求,調用了服務端Speak類的speak方法。
  我們看一下運行結果:

運行結果

三、總結

最后總結一下使用Binder連接池的流程:
(1)為每個業務模塊創建AIDL接口,以及實現其接口的方法。
(2)創建IBinderPool.aidl文件,定義queryBinder(int BinderCode)方法,客戶端通過調用該方法返回特定的Binder對象。
(3)創建BinderPoolService服務端,在onBind方法返回實例化的BinderPool.IBinderPoolImpl對象。
(4)創建BinderPool類,在該類實現客戶端與服務端的連接,解決線程同步的問題,設置Binder的死亡代理等。在onServiceConnected()方法內,獲取到IBinderPool的代理對象。此外,IBinderPool的實現類:IBinderPoolImpl是BinderPool的內部類,實現了IBinderPool.aidl的方法:queryBinder()。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念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

推薦閱讀更多精彩內容