1. 程式人生 > >Android IPC機制(四):細說Binder連線池

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介面,就如一位將軍統帥著千軍。客戶端需要什麼Binder,就提供資訊給Binder連線池,而連線池根據相應資訊返回正確的Binder,這樣客戶端就能執行特定的操作了。可以說,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) {<span style="white-space:pre">	</span>   // 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()。