綜述
前兩篇說到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);
}
在這個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);
}
再創建一個名爲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);
}
然後我們繼承系統自動生成的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;
}
}
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;
}
}
然後我們在看一下這個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;
}
}
}
在這個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;
}
}
下面在看一下客戶端代碼。
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();
}
}
}
在這裏我們要注意使用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>
演示
總結
這樣以來我們就能在我們的應用中只建立一個Service就足夠了。當我們添加一個AIDL接口的時候只需要在BinderPool中添加一個id,然後根據這個id,在BinderPoolImpl中創建一個對應的Binder對象即可。這樣就很大程度上簡化了我們的工作。但是我認爲這樣做依然存在一個問題。那就是當我們創建一個BinderPool對象時,我們的客戶端就已經綁定了Service,之後只是根據不同的id獲取不同的Binder。也就是說從我們綁定Service那時起,這個Service進程就一直在後臺運行,即使這個應用已經不再前臺使用。除非系統將這個Service進程殺死。但是總的來說,在我們的應用中若是用到了很多的AIDL,那麼使用Binder連接池還是很好的選擇。到這裏有關IPC的AIDL內容就說完了。