Android的IPC機制(三)——Binder連線池

yangxi_001發表於2017-12-19

綜述

  前兩篇說到AIDL的使用方法,但是當我們的專案很大時,很多模組都需要用到Service,我們總不能為每一個模組都建立一個Service吧,這樣一來我們的應用就會顯得很笨重。那麼有沒有一種解決方案叫我們只需要建立一個Service,然後去管理AIDL呢?在任玉剛的《Android開發藝術探索》中給出了一個解決方案,那就是Binder連線池。在這裡我們看一下他是怎麼實現的。

Binder連線池的實現

  在前面說到AIDL的使用及原理的時候,我們可以看到在服務端只是建立了一個Binder然後返回給客戶端使用而已。於是我們可以想到是不是我們可以只有一個Service,對於不同可客戶端我們只是去返回一個不同的Binder即可,這樣就避免了建立了大量的Service。在任玉剛的《Android開發藝術探索》給出了一個Binder連線池的概念,很巧妙的避免了Service的多次建立。這個Binder連線池類似於設計模式中的工廠方法模式。為每一個客戶端建立他們所需要的Binder物件。那麼下面我們看一下它是如何實現的。 
  首先我們建立一個名為BinderPool的AIDL介面。

// IBinderPool.aidl
package com.example.ljd_pc.binderpool;

// Declare any non-default types here with import statements

interface IBinderPool {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
     IBinder queryBinder(int binderCode);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

  在這個AIDL介面中我們根據傳入的binderId返回了一個IBinder介面物件。我們再建立兩個AIDL介面作為功能顯示。 
   建立名為ICalculate的AIDL介面用作計算算術的加減法。

// ICalculate.aidl
package com.example.ljd_pc.binderpool;

// Declare any non-default types here with import statements

interface ICalculate {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
     int add(int first, int second);
     int sub(int first, int second);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

  再建立一個名為IRect作為求矩形的面積與周長。

// IRect.aidl
package com.example.ljd_pc.binderpool;

// Declare any non-default types here with import statements

interface IRect {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    int area(int length,int width);
    int perimeter(int length,int width);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

  然後我們繼承系統自動生成的Stub類去實現介面中的方法。下面是ICalculate實現。

package com.example.ljd_pc.binderpool;

import android.os.RemoteException;

public class ICalculateImpl extends ICalculate.Stub {
    @Override
    public int add(int first, int second) throws RemoteException {
        return first + second;
    }

    @Override
    public int sub(int first, int second) throws RemoteException {
        return first - second;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

  IRect的實現。

package com.example.ljd_pc.binderpool;


import android.os.RemoteException;

public class IRectImpl extends IRect.Stub {
    @Override
    public int area(int length, int width) throws RemoteException {
        return length * width;
    }

    @Override
    public int perimeter(int length, int width) throws RemoteException {
        return length*2 + width*2;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

  然後我們在看一下這個BinderPool是怎麼實現的。

package com.example.ljd_pc.binderpool;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;


import java.util.concurrent.CountDownLatch;

public class BinderPool {

    public static final int BINDER_NONE = -1;
    public static final int BINDER_CALCULATE = 0;
    public static final int BINDER_RECT = 1;

    private Context mContext;
    private static IBinderPool mBinderPool;
    private static BinderPool mInstance;
    private CountDownLatch mCountDownLatch;

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

    public static BinderPool getInstance(Context context){
        if (mBinderPool == null){
            synchronized (BinderPool.class){
                if (mBinderPool == null){
                    mInstance = new BinderPool(context);
                }
            }
        }
        return mInstance;
    }

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

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

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

    private synchronized void connectBinderPoolService(){
        mCountDownLatch = new CountDownLatch(1);
        Intent service = new Intent("com.ljd.binder.BINDER_POOL_SERVICE");
        mContext.bindService(service,mBinderPoolConnection,Context.BIND_AUTO_CREATE);
        try {
            mCountDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

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

    public static class BinderPoolImpl extends IBinderPool.Stub {

        public BinderPoolImpl(){
            super();
        }
        @Override
        public IBinder queryBinder(int binderCode) throws RemoteException {
            IBinder binder = null;
            switch (binderCode){
                case BINDER_CALCULATE:
                    binder = new ICalculateImpl();
                    break;
                case BINDER_RECT:
                    binder = new IRectImpl();
                    break;
                default:
                    break;
            }
            return binder;
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112

  在這個BinderPool中我們首先為這兩個AIDL介面建立一個id。又建立了一個BinderPoolImpl的內部類,這個BinderPoolImpl內部類根據傳入的id返回相對應的Binder。在BinderPool中採用單例項模式,而在BinderPool的構造方法中繫結了Service。對客戶端提供了一個queryBinder方法來獲取所需要的Binder物件。而對於服務端只需要new BinderPool.BinderPoolImpl()即可。從這我們就可以看出這個BinderPool也就是對客戶端和服務端的程式碼進行了一次封裝。然後進行對不同的客戶端在Service中返回不同的Binder。由於AIDL支援多執行緒併發訪問的,所以在繫結Service中做了採用synchronized和CountDownLatch做了執行緒同步處理。 
  由於BinderPool對程式碼進行了封裝,所以服務端的程式碼就很簡單了,只需要new BinderPool.BinderPoolImpl()返回即可。

package com.example.ljd_pc.binderpool;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;

public class BinderPoolService extends Service {
    private Binder mBinderPool = new BinderPool.BinderPoolImpl();

    public BinderPoolService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinderPool;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

  下面在看一下客戶端程式碼。

package com.example.ljd_pc.binderpool;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import butterknife.Bind;
import butterknife.ButterKnife;

public class BinderPoolActivity extends AppCompatActivity implements View.OnClickListener{

    @Bind(R.id.add_btn)
    Button addBtn;

    @Bind(R.id.sub_btn)
    Button subBtn;

    @Bind(R.id.area_btn)
    Button areaBtn;

    @Bind(R.id.per_btn)
    Button perBtn;
    private final String TAG = "BinderPoolActivity";

    private BinderPool mBinderPool;
    private ICalculate mCalculate;
    private IRect mRect;
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            addBtn.setOnClickListener(BinderPoolActivity.this);
            subBtn.setOnClickListener(BinderPoolActivity.this);
            areaBtn.setOnClickListener(BinderPoolActivity.this);
            perBtn.setOnClickListener(BinderPoolActivity.this);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_binder_pool);
        ButterKnife.bind(this);
        getBinderPool();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ButterKnife.unbind(this);
    }

    private void getBinderPool(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                mBinderPool = BinderPool.getInstance(BinderPoolActivity.this);
                mHandler.obtainMessage().sendToTarget();
            }
        }).start();
    }

    @Override
    public void onClick(final View v) {
        try {
            switch (v.getId()){
                case R.id.add_btn:
                    mCalculate = ICalculateImpl.asInterface(mBinderPool.queryBinder(BinderPool.BINDER_CALCULATE));
                    Log.e(TAG,String.valueOf(mCalculate.add(3,2)));
                    Toast.makeText(BinderPoolActivity.this,String.valueOf(mCalculate.add(3,2)),Toast.LENGTH_SHORT).show();
                    break;
                case R.id.sub_btn:
                    mCalculate = ICalculateImpl.asInterface(mBinderPool.queryBinder(BinderPool.BINDER_CALCULATE));
                    Log.e(TAG,String.valueOf(mCalculate.sub(3,2)));
                    Toast.makeText(BinderPoolActivity.this,String.valueOf(mCalculate.sub(3,2)),Toast.LENGTH_SHORT).show();
                    break;
                case R.id.area_btn:
                    mRect = IRectImpl.asInterface(mBinderPool.queryBinder(BinderPool.BINDER_RECT));
                    Log.e(TAG,String.valueOf(mRect.area(3,2)));
                    Toast.makeText(BinderPoolActivity.this,String.valueOf(mRect.area(3,2)),Toast.LENGTH_SHORT).show();
                    break;
                case R.id.per_btn:
                    mRect = IRectImpl.asInterface(mBinderPool.queryBinder(BinderPool.BINDER_RECT));
                    Log.e(TAG,String.valueOf(mRect.perimeter(3,2)));
                    Toast.makeText(BinderPoolActivity.this,String.valueOf(mRect.perimeter(3,2)),Toast.LENGTH_SHORT).show();
                    break;
                default:
                    break;
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101

  在這裡我們要注意使用CountDownLatch對bindService這一非同步操作變為同步,所以在獲取BinderPool物件時不能再主執行緒中操作。並且在這一步中作者建議我們在Application中去初始化這個BinderPool物件。 
  佈局程式碼如下。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical">

    <Button
        android:id="@+id/add_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/add"
        />
    <Button
        android:id="@+id/sub_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/sub"/>
    <Button
        android:id="@+id/area_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/area"/>
    <Button
        android:id="@+id/per_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/per"/>
</LinearLayout>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

演示

這裡寫圖片描述

總結

  這樣以來我們就能在我們的應用中只建立一個Service就足夠了。當我們新增一個AIDL介面的時候只需要在BinderPool中新增一個id,然後根據這個id,在BinderPoolImpl中建立一個對應的Binder物件即可。這樣就很大程度上簡化了我們的工作。但是我認為這樣做依然存在一個問題。那就是當我們建立一個BinderPool物件時,我們的客戶端就已經繫結了Service,之後只是根據不同的id獲取不同的Binder。也就是說從我們繫結Service那時起,這個Service程式就一直在後臺執行,即使這個應用已經不再前臺使用。除非系統將這個Service程式殺死。但是總的來說,在我們的應用中若是用到了很多的AIDL,那麼使用Binder連線池還是很好的選擇。到這裡有關IPC的AIDL內容就說完了。

原始碼下載

轉自:http://blog.csdn.net/ljd2038/article/details/50733153

相關文章