《Android開發藝術探索》學習總結7 Android中為實現IPC時Binder連線池的使用
學習基礎和材料來源於《Android開發藝術探索》;本篇主要簡單介紹一下在Android中實現IPC是Binder連線池的使用;原理性的東西介紹不多主要介紹一下使用場景和使用步驟;其實看懂下面兩張圖感覺就沒有什麼好說的;除了Binder連線池核心程式碼注意一下,其他的都是最基礎的知識。
1,常見的服務配合AIDL的使用場景
如上圖所示,服務端有n個aidl介面,每一個aidl介面對應一個服務,客戶端有m個aidl介面,那麼,如果想在客戶端獲取p個aidl介面,那麼服務端就得建立對應的p個服務的例項,這是我們經常使用的情況,也沒有什麼問題,一個服務用來維護一個功能模組也很合理。
2,服務配合AIDL使用的特殊場景
如上圖所示,現在服務端有n個aidl介面(對應於n個互不耦合的功能模組);有m個客戶端,依次需要服務端的n個模組功能中p1,p2,...,pm個;如果按照第一種方式,每一個功能模組對應於一個服務,那麼我們需要最多建立m*n個服務例項;這個數量很大,我先只想建立一個服務例項來管理所有的功能模組,下面就要使用到Binder連線池的技術了。
上圖中,我也給出了Binder連線池的思想:
(1)構建一個管理所有其他功能模組(aidl介面)的管理模組(aidl介面),提供一個根據功能模組標識碼獲取對應aidl介面例項的功能函式
(2)在每一個客戶端建立一個Binder連線池,在構造器中完成服務繫結工作,從而獲取管理模組(aidl介面)例項,在自定義的方法傳遞服務端功能模組的標識碼給管理模組的上述功能函式,即可獲取對應模組功能標識碼的介面例項;
聽起來很抽象,其實真的很簡單,下面就來說一下實現步驟:
服務端:
第一步:在服務端建立多個aidl檔案,Build工程生成對應介面,
第二步:自定義對應數目的java類分別繼承上述生成接口裡面的靜態內部類Stub,並實現在aidl中定義的介面方法
第三步:在定義一個管理上述aidl介面的aidl檔案,Build一下工程,生成一個對應介面
第四步:建立一個服務,在裡面建立一個內部類繼承上述aidl介面中的Stub靜態類,並實現裡面的介面方法(主要就是根據功能模組標識碼返回對應功能模組的aidl介面例項)
至此,服務端就完成了。
客戶端:
第一步:根據功能需求,複製部分或者全部服務端的aidl檔案至客戶端工程並Build一下工程
第二步:複製服務端管理模組的aidl檔案至客戶端工程並Build一下工程
第三步:建立Binder連線池,實現裡面的幾個業務功能,特別注意裡面定義的功能模組標識碼一定要以服務端定義的為準
第四步:建立Binder連線池例項,呼叫裡面的自定義函式並傳入在服務端中定義的功能模組標識碼即可獲取相應功能模組的aidl介面例項
至此,客戶端就完成了。
3,程式碼示例
如果還是感覺很模糊,我只能用一個很簡單例項來演示一下了;這個示例包含一個服務端,兩個客戶端共三個應用,服務端中有兩個功能模組,客戶端1中兩個功能模組都需要,客戶端2中只需要其中一個功能模組,下面來看具體的程式碼:
服務端:
服務端程式碼結構如下:
IPayInterface.aidl和ITravelInterface.aidl檔案內容如下:
// IPayInterface.aidl
package com.hfut.operationbinderpool;
// Declare any non-default types here with import statements
interface IPayInterface {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
boolean pay(int money);
}
// ITravelInterface.aidl
package com.hfut.operationbinderpool;
// Declare any non-default types here with import statements
interface ITravelInterface {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
String toWhere(String destination);
}
IBinderPool.aidl檔案內容如下:
// IBinderPool.aidl
package com.hfut.operationbinderpool;
// 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 getAidlInterface(int interfaceCode);
}
MyPayInterface.java程式碼:
import com.hfut.operationbinderpool.IPayInterface;
/**
* author:why
* created on: 2018/7/18 8:53
* description:
*/
public class MyPayInerface extends IPayInterface.Stub {
@Override
public boolean pay(int money) throws RemoteException {
if(money>100){
return true;
}
else{
return false;
}
}
}
MyTravelInterface.java程式碼:
package com.hfut.operationbinderpool.aidlInterface;
import android.os.RemoteException;
import com.hfut.operationbinderpool.ITravelInterface;
/**
* author:why
* created on: 2018/7/18 8:57
* description:
*/
public class MyTravelInterface extends ITravelInterface.Stub {
@Override
public String toWhere(String destination) throws RemoteException {
return "去"+destination+"請做車";
}
}
MyService.java程式碼:
package com.hfut.operationbinderpool;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import com.hfut.operationbinderpool.IBinderPool.Stub;
import com.hfut.operationbinderpool.aidlInterface.MyPayInerface;
import com.hfut.operationbinderpool.aidlInterface.MyTravelInterface;
/**
* @author why
* @date 2018-7-18 9:02:38
*/
public class MyService extends Service {
public static final int PAY_INTERFACE_CODE = 0;
public static final int TRAVEL_INTERFACE_CODE = 1;
public MyService() {
}
private Binder bindInterface = new IBinderPool.Stub() {
@Override
public IBinder getAidlInterface(int interfaceCode) throws RemoteException {
Binder binder = null;
switch (interfaceCode) {
case PAY_INTERFACE_CODE:
binder = new MyPayInerface();
break;
case TRAVEL_INTERFACE_CODE:
binder = new MyTravelInterface();
break;
default:
break;
}
return binder;
}
};
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return bindInterface;
}
}
MainActivity.java程式碼:
package com.hfut.operationbinderpool;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
/**
* @author why
* @date 2018-7-18 8:47:32
*/
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
主配置AndroidManifest.xml檔案程式碼:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hfut.operationbinderpool">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".MyService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="hfut.com.why"></action>
</intent-filter>
</service>
</application>
</manifest>
客戶端1:
客戶端1程式碼結構如下:
其中功能模組和管理模組內容和服務端一模一樣,這裡不再新增;
BinderPool.java程式碼:
package com.hfut.operationbinderdown;
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 android.util.Log;
import com.hfut.operationbinderpool.IBinderPool;
import java.util.concurrent.CountDownLatch;
/**
* author:why
* created on: 2018/7/18 9:20
* description:
*/
public class BinderPool {
private static final String TAG = "BinderPool";
private static final String ACTION = "hfut.com.why";
private static final String PACKAGENAME = "com.hfut.operationbinderpool";
public static final int PAY_INTERFACE_CODE = 0;
public static final int TRAVEL_INTERFACE_CODE = 1;
private Context mContext;
private IBinderPool mBinderPool;
private static volatile BinderPool sInstance;
private CountDownLatch mConnectBinderPoolCountDownLatch;
private BinderPool(Context context) {
mContext = context.getApplicationContext();
connectBinderPoolService();
}
public static BinderPool getInsance(Context context) {
if (sInstance == null) {
synchronized (BinderPool.class) {
if (sInstance == null) {
sInstance = new BinderPool(context);
}
}
}
return sInstance;
}
private synchronized void connectBinderPoolService() {
mConnectBinderPoolCountDownLatch = new CountDownLatch(1);
Intent service = new Intent();
service.setAction(ACTION);
service.setPackage(PACKAGENAME);
mContext.bindService(service, mBinderPoolConnection,
Context.BIND_AUTO_CREATE);
try {
mConnectBinderPoolCountDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* query binder by binderCode from binder pool
*
* @param binderCode the unique token of binder
* @return binder who's token is binderCode<br>
* return null when not found or BinderPoolService died.
*/
public IBinder getAidlInterface(int binderCode) {
IBinder binder = null;
try {
if (mBinderPool != null) {
binder = mBinderPool.getAidlInterface(binderCode);
}
} catch (RemoteException e) {
e.printStackTrace();
}
return binder;
}
private ServiceConnection mBinderPoolConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
// ignored.
}
@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() {
@Override
public void binderDied() {
Log.w(TAG, "binder died.");
mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0);
mBinderPool = null;
connectBinderPoolService();
}
};
}
MainActivity.java程式碼:
package com.hfut.operationbinderdown;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import com.hfut.operationbinderpool.IBinderPool;
import com.hfut.operationbinderpool.IPayInterface;
import com.hfut.operationbinderpool.ITravelInterface;
/**
* @author why
* @date 2018-7-18 9:14:24
*/
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(){
@Override
public void run() {
test();
}
}.start();
}
private void test() {
BinderPool pool=BinderPool.getInsance(this);
IBinder payBinderInterface= pool.getAidlInterface(BinderPool.PAY_INTERFACE_CODE);
IPayInterface payInterface=IPayInterface.Stub.asInterface(payBinderInterface);
try {
Log.d(TAG, "test: "+payInterface.pay(50));
} catch (RemoteException e) {
e.printStackTrace();
}
IBinder travelBinderInterface=pool.getAidlInterface(BinderPool.TRAVEL_INTERFACE_CODE);
ITravelInterface travelInterface=ITravelInterface.Stub.asInterface(travelBinderInterface);
try {
Log.d(TAG, "test: "+travelInterface.toWhere("上海"));
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
主配置AndroidManifest.xml檔案內容:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hfut.operationbinderdown">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
客戶端2:
客戶端2程式碼結構:
其中功能模組和管理模組內容和服務端一模一樣,BinderPool和客戶端基本一樣,除了包名:
BinderPool程式碼:
package com.hfut.binderpooltestdown1;
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 android.util.Log;
import com.hfut.operationbinderpool.IBinderPool;
import java.util.concurrent.CountDownLatch;
/**
* author:why
* created on: 2018/7/18 9:20
* description:
*/
public class BinderPool {
private static final String TAG = "BinderPool";
private static final String ACTION = "hfut.com.why";
private static final String PACKAGENAME = "com.hfut.operationbinderpool";
public static final int PAY_INTERFACE_CODE = 0;
public static final int TRAVEL_INTERFACE_CODE = 1;
private Context mContext;
private IBinderPool mBinderPool;
private static volatile BinderPool sInstance;
private CountDownLatch mConnectBinderPoolCountDownLatch;
private BinderPool(Context context) {
mContext = context.getApplicationContext();
connectBinderPoolService();
}
public static BinderPool getInsance(Context context) {
if (sInstance == null) {
synchronized (BinderPool.class) {
if (sInstance == null) {
sInstance = new BinderPool(context);
}
}
}
return sInstance;
}
private synchronized void connectBinderPoolService() {
mConnectBinderPoolCountDownLatch = new CountDownLatch(1);
Intent service = new Intent();
service.setAction(ACTION);
service.setPackage(PACKAGENAME);
mContext.bindService(service, mBinderPoolConnection,
Context.BIND_AUTO_CREATE);
try {
mConnectBinderPoolCountDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* query binder by binderCode from binder pool
*
* @param binderCode the unique token of binder
* @return binder who's token is binderCode<br>
* return null when not found or BinderPoolService died.
*/
public IBinder getAidlInterface(int binderCode) {
IBinder binder = null;
try {
if (mBinderPool != null) {
binder = mBinderPool.getAidlInterface(binderCode);
}
} catch (RemoteException e) {
e.printStackTrace();
}
return binder;
}
private ServiceConnection mBinderPoolConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
// ignored.
}
@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() {
@Override
public void binderDied() {
Log.w(TAG, "binder died.");
mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0);
mBinderPool = null;
connectBinderPoolService();
}
};
}
MainActivity.java程式碼:
package com.hfut.binderpooltestdown1;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import com.hfut.operationbinderpool.ITravelInterface;
/**
* @author why
* @date 2018-7-18 9:54:47
*/
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(){
@Override
public void run() {
test();
}
}.start();
}
private void test() {
BinderPool pool=BinderPool.getInsance(this);
// IBinder payBinderInterface= pool.getAidlInterface(BinderPool.PAY_INTERFACE_CODE);
// IPayInterface payInterface=IPayInterface.Stub.asInterface(payBinderInterface);
// try {
// Log.d(TAG, "test: "+payInterface.pay(50));
// } catch (RemoteException e) {
// e.printStackTrace();
// }
IBinder travelBinderInterface=pool.getAidlInterface(BinderPool.TRAVEL_INTERFACE_CODE);
ITravelInterface travelInterface=ITravelInterface.Stub.asInterface(travelBinderInterface);
try {
Log.d(TAG, "test: "+travelInterface.toWhere("南京"));
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
主配置檔案AndroidManifest.xml內容:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hfut.binderpooltestdown1">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
4,執行結果
執行服務端應用,執行客戶端1應用,在執行客戶端應用,檢視日誌如下: