Android Binder學(xué)習(xí)筆記(一)接上篇添加圖書和獲取圖書列表的例子。
向服務(wù)端注冊成為觀察者
我們希望每當(dāng)服務(wù)端有新書到來的時候能主動通知客戶端,這樣就不需要客戶端去輪詢了。
首先聲明一個AIDL接口,用來注冊、通知觀察者。這里選擇使用AIDL接口是因為在AIDL中無法使用普通接口。
IOnNewBookArriveListener.aidl
// IOnNewBookArriveListener.aidl
package com.hm.aidlserver;
import com.hm.aidlserver.Book;
// Declare any non-default types here with import statements
interface IOnNewBookArriveListener {
void onNewBookArrived(in Book newBook);
}
修改IBookManager.aidl
// IBookManager.aidl
package com.hm.aidlserver;
import com.hm.aidlserver.Book;
import com.hm.aidlserver.IOnNewBookArriveListener;
interface IBookManager {
List<Book>getBookList();
void addBook(in Book book);
//注冊觀察者
void registerListener(IOnNewBookArriveListener listener);
//取消注冊
void unRegisterListener(IOnNewBookArriveListener listener);
}
修改服務(wù)端的Service,實現(xiàn)registerListener和unRegisterListener方法
public class BookManagerService extends Service {
//...
private RemoteCallbackList<IOnNewBookArriveListener> mListenerList = new RemoteCallbackList<>();
private Binder mBinder = new IBookManager.Stub() {
//...
@Override
public void registerListener(IOnNewBookArriveListener listener) throws RemoteException {
mListenerList.register(listener);
}
@Override
public void unRegisterListener(IOnNewBookArriveListener listener) throws RemoteException {
mListenerList.unregister(listener);
}
//...
};
//...
/**
* 如果IOnNewBookArriveListener#onNewBookArrived(Book newBook)方法是耗時方法的話
* notifyBookArrived不能運行在服務(wù)端的住線程
* 通知觀察者有新書到來
*/
private void notifyBookArrived(Book book) throws RemoteException {
mBookList.add(book);
final int N = mListenerList.beginBroadcast();
for (int i = 0; i < N; i++) {
IOnNewBookArriveListener l = mListenerList.getBroadcastItem(i);
if (l != null) {
l.onNewBookArrived(book);
}
}
mListenerList.finishBroadcast();
}
//...
}
修改客戶端的實現(xiàn)
private IBookManager bookManager;
private IOnNewBookArriveListener listener = new IOnNewBookArriveListener.Stub() {
@Override
public void onNewBookArrived(Book newBook) throws RemoteException {
//當(dāng)前線程是在客戶端的Binder線程池中運行的
Log.e(TAG, "onNewBookArrived: " + Thread.currentThread().getName());
//如果需要更新UI的話,使用handler的方式來實現(xiàn)
mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook).sendToTarget();
}
};
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e(TAG, "onServiceConnected:");
bookManager = IBookManager.Stub.asInterface(service);
try {
getBookList();
//向服務(wù)端注冊成為觀察者
bookManager.registerListener(listener);
} catch (RemoteException e) {
e.printStackTrace();
Log.e(TAG, "onServiceConnected: error" + e.getMessage());
}
}
};
注意兩點:
如果明確知道某個遠(yuǎn)程方法是耗時的,客戶端就要避免在UI線程中去調(diào)用遠(yuǎn)程方法。
客戶端的onServiceConnected方法和onServiceDisconnected是運行在主線程的,所以也不能在這兩個方法中調(diào)用遠(yuǎn)程方法。
當(dāng)遠(yuǎn)程服務(wù)端調(diào)用客戶端的listener中的方法時,如果被調(diào)用的方法也是耗時方法,那么也不能在服務(wù)端的主線程調(diào)用。例如:如果IOnNewBookArriveListener#onNewBookArrived(Book newBook)方法是耗時方法的話,我們也不能在服務(wù)端的主線程調(diào)用。
Binder死亡時,重新連接服務(wù)
- 使用IBinder.DeathRecipient
/**
* 當(dāng)Binder死亡時,我們會收到通知,這個時候,我們可以重新發(fā)起連接請求從而恢復(fù)鏈接。
*/
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.e(TAG, "binderDied: " + Thread.currentThread().getName());
if (bookManager != null) {
bookManager.asBinder().unlinkToDeath(mDeathRecipient, 0);
bookManager = null;
}
//重新連接
bind();
}
};
//...
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e(TAG, "onServiceConnected:");
bookManager = IBookManager.Stub.asInterface(service);
try {
//在這里設(shè)置DeathRecipient監(jiān)聽
service.linkToDeath(mDeathRecipient, 0);
getBookList();
bookManager.registerListener(mOnNewBookArriveListener);
} catch (RemoteException e) {
e.printStackTrace();
Log.e(TAG, "onServiceConnected: error" + e.getMessage());
}
}
};
如果我們設(shè)置了DeathRecipient監(jiān)聽,當(dāng)Binder死亡的時候,我們會收到binderDied方法的回調(diào),在binderDied方法中我們可以重連遠(yuǎn)程服務(wù)。
- 在ServiceConnection的onServiceDisconnected方法中重新連接
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.e(TAG, "onServiceConnected:");
bookManager = IBookManager.Stub.asInterface(service);
try {
getBookList();
bookManager.registerListener(mOnNewBookArriveListener);
} catch (RemoteException e) {
e.printStackTrace();
Log.e(TAG, "onServiceConnected: error" + e.getMessage());
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
bookManager = null;
Log.e(TAG, "onServiceDisconnected: " + Thread.currentThread().getName());
//重新連接
bind();
}
};
這兩者的區(qū)別在于ServiceConnection的onServiceDisconnected方法運行在客戶端的主線程;IBinder.DeathRecipient 的binderDied方法運行在客戶端的Binder線程池中。
跨進(jìn)程通信使用權(quán)限驗證功能
第一種驗證方式
我們可以在服務(wù)端的Service的onBind方法中驗證,驗證不通過就返回null。這樣客戶端就無法綁定服務(wù)。驗證方式有多種,比如使用permission驗證。使用這種方式,首先要在AndridManifest.xml文件中聲明綁定服務(wù)所需要權(quán)限,比如
<permission
android:name="com.hm.aidlserver.permission.ACCESS_BOOK_SERVICE"
android:protectionLevel="normal"/>
BookManagerService的onBind方法
@Override
public IBinder onBind(Intent intent) {
//在這里做權(quán)限驗證,如果驗證不通過就返回null
int check = checkCallingOrSelfPermission("com.hm.aidlserver.permission.ACCESS_BOOK_SERVICE");
if (check == PackageManager.PERMISSION_DENIED) {
Log.e(TAG, "onBind: permission denied");
return null;
}
Log.e(TAG, "onBind: permission granted");
return mBinder;
}
如果我們想要成功綁定服務(wù)端的服務(wù),就需要在客戶端的AndridManifest.xml中聲明所需要的權(quán)限
<uses-permission android:name="com.hm.aidlserver.permission.ACCESS_BOOK_SERVICE"/>
第二種驗證方式
在服務(wù)端的Binder中的onTransact方法中驗證,如果驗證不通過,返回false。這樣服務(wù)端就不會執(zhí)行AIDL中的方法,從而達(dá)到保護(hù)服務(wù)端的效果。驗證方式有很多,我們在下面的方法中我們驗證了permission和包名。
private Binder mBinder = new IBookManager.Stub() {
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
/**
* 驗證permission
*/
Log.e(TAG, "onTransact 驗證權(quán)限");
int check = checkCallingOrSelfPermission("com.hm.aidlserver.permission.ACCESS_BOOK_SERVICE");
if (check == PackageManager.PERMISSION_DENIED) {
Log.e(TAG, "onTransact: permission denied");
return false;
}
/**
* 驗證包名
*/
String packageName;
String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
if (packages != null && packages.length > 0) {
packageName = packages[0];
if (!packageName.startsWith("com.hm")) {
Log.e(TAG, "onTransact: package verify failed");
return false;
}
} else {
return false;
}
return super.onTransact(code, data, reply, flags);
}
};
Binder連接池
Binder連接池的主要作用就是將客戶端的每個業(yè)務(wù)模塊的Binder請求統(tǒng)一發(fā)送到遠(yuǎn)程Service中去執(zhí)行,從而避免了重復(fù)創(chuàng)建Service的過程,不然的話,每個Binder對象就得對應(yīng)一個Service。
在這種模式下,整個工作流程是這樣的:服務(wù)端提供一個queryBinder接口,這個接口能夠根據(jù)業(yè)務(wù)模塊的標(biāo)志來返回相應(yīng)的Binder對象給客戶端,客戶端拿到所需的Binder對象后就可以進(jìn)行遠(yuǎn)程方法調(diào)用了。對服務(wù)端來說,只需要一個Service就可以了。
舉個例子:客戶端中的一個業(yè)務(wù)模塊需要遠(yuǎn)程調(diào)用加解密字符串的功能,另一個業(yè)務(wù)模塊需要使用計算加法的功能。
聲明兩個AIDL文件
ISecurityCenter.aidl
// ISecurityCenter.aidl
package com.hm.aidlserver;
// Declare any non-default types here with import statements
interface ISecurityCenter {
String encrypt(String content);
String decrypt(String password);
}
ICompute.aidl
// ICompute.aidl
package com.hm.aidlserver;
// Declare any non-default types here with import statements
interface ICompute {
int add(int a,int b);
}
兩個接口的實現(xiàn)
SecurityCenterImpl
public class SecurityCenterImpl extends ISecurityCenter.Stub {
private static final String TAG = "SecurityCenterImpl";
private static final char SECRET_CODE = '^';
@Override
public String encrypt(String content) throws RemoteException {
char[] chars = content.toCharArray();
for (int i = 0; i < chars.length; i++) {
chars[i] ^= SECRET_CODE;
}
return new String(chars);
}
@Override
public String decrypt(String password) throws RemoteException {
return encrypt(password);
}
}
IComputeImpl
public class IComputeImpl extends ICompute.Stub {
private static final String TAG = "IComputeImpl";
@Override
public int add(int a, int b) throws RemoteException {
return a + b;
}
}
現(xiàn)在不同業(yè)務(wù)模塊需要的AIDL接口和實現(xiàn)已經(jīng)完成了,注意這里并沒有為每個模塊的AIDL單獨創(chuàng)建Service,接下來就是服務(wù)端和Binder連接池的工作了。
首先為Binder連接池創(chuàng)建AIDL接口IBinderPool.aidl
IBinderPool.aidl
// IBinderPool.aidl
package com.hm.aidlserver;
// Declare any non-default types here with import statements
interface IBinderPool {
//根據(jù)不同的查詢碼返回相應(yīng)的Binder對象
IBinder queryBinder(int binderCode);
}
接下來在服務(wù)端定義一個Service用來返回IBinderPool
import com.hm.aidlserver.impl.IComputeImpl;
import com.hm.aidlserver.impl.SecurityCenterImpl;
public class BinderPoolService extends Service {
private static final String TAG = "BinderPoolService";
private Binder mBinderPool = new BinderPool();
public BinderPoolService() {
}
@Override
public IBinder onBind(Intent intent) {
Log.e(TAG, "onBind: ");
return mBinderPool;
}
//聲明BinderPool類實現(xiàn)IBinderPool接口
private static class BinderPool extends IBinderPool.Stub {
private static final String TAG = "BinderPool";
private static final int BINDERT_COMPUTE = 0;
private static final int BINDERT_NOSECURITY_CENTER = 1;
private BinderPool() {
Log.e(TAG, "調(diào)用構(gòu)造函數(shù)");
}
@Override
public IBinder queryBinder(int binderCode) throws RemoteException {
IBinder binder = null;
switch (binderCode) {
case BINDERT_NOSECURITY_CENTER:
binder = new SecurityCenterImpl();
break;
case BINDERT_COMPUTE:
binder = new IComputeImpl();
break;
}
return binder;
}
}
}
接下來在客戶端實現(xiàn)具體的Binder連接池,在它的內(nèi)部首先要去綁定遠(yuǎn)程服務(wù),綁定成功后,客戶端就可以通過它的queryBinder方法去獲取各自對應(yīng)的Binder,拿到所需的Binder以后,不同的業(yè)務(wù)模塊就可以進(jìn)行各自的操作了。
/**
* Created by dumingwei on 2017/5/7.
* Binder連接池的具體實現(xiàn)
*/
public class BinderPoolHelper {
private static final String TAG = "BinderPoolHelper";
public static final int BINDERT_COMPUTE = 0;
public static final int BINDERT_NOSECURITY_CENTER = 1;
private Context mContext;
private IBinderPool mBinderPool;
private static volatile BinderPoolHelper sInstance;
private CountDownLatch mConnectBinderPoolCountDownLatch;
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();
}
mConnectBinderPoolCountDownLatch.countDown();
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.e(TAG, "binderDied: ");
mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0);
mBinderPool = null;
connectBinderPoolService();
}
};
private BinderPoolHelper(Context context) {
mContext = context.getApplicationContext();
connectBinderPoolService();
}
public static BinderPoolHelper getsInstance(Context context) {
if (sInstance == null) {
synchronized (BinderPoolHelper.class) {
if (sInstance == null) {
sInstance = new BinderPoolHelper(context);
}
}
}
return sInstance;
}
private synchronized void connectBinderPoolService() {
mConnectBinderPoolCountDownLatch = new CountDownLatch(1);
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.hm.aidlserver", "com.hm.aidlserver.BinderPoolService"));
mContext.bindService(intent, mBinderPoolConnection, Context.BIND_AUTO_CREATE);
try {
mConnectBinderPoolCountDownLatch.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;
}
}
使用
private ISecurityCenter mSecurityCenter;
private ICompute mComputer;
private BinderPoolHelper binderPoolHelper;
public void onViewClicked(View view) {
switch (view.getId()) {
case R.id.btn_ISecurityCenter:
new Thread(new Runnable() {
@Override
public void run() {
binderPoolHelper = BinderPoolHelper.getsInstance(BinderPoolActivity.this);
IBinder securityBinder = binderPoolHelper.queryBinder(BinderPoolHelper.BINDERT_NOSECURITY_CENTER);
mSecurityCenter = SecurityCenterImpl.asInterface(securityBinder);
Log.e(TAG, "doWork: visit ISecurityCenter");
String msg = "helloworld-安卓";
String password;
try {
password = mSecurityCenter.encrypt(msg);
Log.e(TAG, "doWork: encrypt" + password);
String content = mSecurityCenter.decrypt(password);
Log.e(TAG, "doWork: decrypt" + content);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}).start();
break;
case R.id.btn_ICompute:
new Thread(new Runnable() {
@Override
public void run() {
binderPoolHelper = BinderPoolHelper.getsInstance(BinderPoolActivity.this);
IBinder computeBinder = binderPoolHelper.queryBinder(BinderPoolHelper.BINDERT_COMPUTE);
mComputer = IComputeImpl.asInterface(computeBinder);
Log.e(TAG, "doWork: visit ICompute");
try {
Log.e(TAG, "doWork: " + mComputer.add(7, 14));
} catch (RemoteException e) {
e.printStackTrace();
}
}
}).start();
break;
default:
break;
}
}
Binder中定向tag in out inout的結(jié)論
Binder中的定向 tag 表示了在跨進(jìn)程通信中數(shù)據(jù)的流向,其中 in 表示數(shù)據(jù)只能由客戶端流向服務(wù)端, out 表示數(shù)據(jù)只能由服務(wù)端流向客戶端,而 inout 則表示數(shù)據(jù)可在服務(wù)端與客戶端之間雙向流通。
- in 為定向 tag 的話表現(xiàn)為服務(wù)端將會接收到一個那個對象的完整數(shù)據(jù),但是客戶端的那個對象不會因為服務(wù)端對傳參的修改而發(fā)生變動;
- out 的話表現(xiàn)為服務(wù)端將會接收到那個對象,但是對象的各個字段都是默認(rèn)值了。在服務(wù)端對接收到的對象有任何修改之后客戶端將會同步變動;
- inout 為定向 tag 的情況下,服務(wù)端將會接收到客戶端傳來對象的完整信息,并且在服務(wù)端對接收到的對象有任何修改之后客戶端將會同步變動;
Binder的優(yōu)勢

Android 進(jìn)程間通信的其他方式
使用Bundle。
使用文件共享,兩個進(jìn)程通過讀/寫同一個文件來交換數(shù)據(jù)。(并發(fā)讀寫不好處理同步問題)。
使用Messenger:Messenger可以譯為信使。通過它可以在不同的進(jìn)程中傳遞Message對象。在Message中放入我們需要傳遞的數(shù)據(jù),就可以輕松地實現(xiàn)數(shù)據(jù)的進(jìn)程間傳遞了。Messenger是一種輕量級的IPC方案,它的底層實現(xiàn)是AIDL。同時Messenger一次處理一個請求,在服務(wù)端不用考慮線程同步的問題。Messenger的主要作用是為了傳遞消息,很多時候我們可能需要跨進(jìn)程調(diào)用服務(wù)端的方法。這種情況下Messenger就無法做到了。
使用ContentProvider。
使用Socket。
各種方式適用場景對比。

參考鏈接
- 《Android開發(fā)藝術(shù)探索》
- 你真的理解AIDL中的in,out,inout么?