Android Binder的使用
?Binder是一個深入的話題,由于Binder太過于復雜,所以本文不涉及底層細節,要想要知道底層細節可以去閱讀Android Bander設計與實現 - 設計篇、寫給 Android 應用工程師的 Binder 原理剖析這兩篇文章。
1、AIDL文件的創建及解析
?Binder使用起來還是比較簡單的,創建一個IBinderPool.aidl文件然后clean一下,就可以給我們生成一個Java文件。
// IBinderPool.aidl package com.example.binder.aidl; interface IBinderPool {IBinder queryBinder(int binderCode); } 復制代碼?生成的Java文件如下:
/** This file is auto-generated. DO NOT MODIFY.* Original file: D:\\AndroidDemo\\BinderDemo\\app\\src\\main\\aidl\\com\\example\\binder\\aidl\\IBinderPool.aidl*/ package com.example.binder.aidl; // Declare any non-default types here with import statementspublic interface IBinderPool extends android.os.IInterface {/*** Local-side IPC implementation stub class.*/public static abstract class Stub extends android.os.Binder implements com.example.binder.aidl.IBinderPool {//Binder的唯一標識符,一般用當前Binder的類名表示,比如這里的com.example.binder.aidl.IBinderPoolprivate static final java.lang.String DESCRIPTOR = "com.example.binder.aidl.IBinderPool";/*** Construct the stub at attach it to the interface.* */public Stub() {this.attachInterface(this, DESCRIPTOR);}/*** Cast an IBinder object into an com.example.binder.aidl.IBinderPool interface,* generating a proxy if needed.* 用于將服務端的Binder對象轉換成客戶端所需的AIDL接口類型對象,這種類型轉換過程是區分進程的,如果客戶端和服務端位于同一進程,那么此方法返回的就是服務端的Stub對象本身,否則返回的是系統封裝后的Stub.proxy對象*/public static com.example.binder.aidl.IBinderPool asInterface(android.os.IBinder obj) {if ((obj == null)) {return null;}//查詢本地接口android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);//如果存在直接返回該對象(代表服務端與客戶端在同一進程)if (((iin != null) && (iin instanceof com.example.binder.aidl.IBinderPool))) {return ((com.example.binder.aidl.IBinderPool) iin);}//返回系統封裝后的Stub.proxy對象(代表服務端客戶端不在同一進程)return new com.example.binder.aidl.IBinderPool.Stub.Proxy(obj);}//返回當前的Binder對象public android.os.IBinder asBinder() {return this;}//該方法運行在服務端的Binder線程池中,當客戶端發起跨進程請求時,遠程請求會通過系統底層封裝后交由此方法處理public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {switch (code) {case INTERFACE_TRANSACTION: {reply.writeString(DESCRIPTOR);return true;}case TRANSACTION_queryBinder: {data.enforceInterface(DESCRIPTOR);int _arg0;_arg0 = data.readInt();android.os.IBinder _result = this.queryBinder(_arg0);reply.writeNoException();reply.writeStrongBinder(_result);//返回false則代表客戶端請求失敗,可以根據特性來做權限驗證return true;}}return super.onTransact(code, data, reply, flags);}private static class Proxy implements com.example.binder.aidl.IBinderPool {private android.os.IBinder mRemote;Proxy(android.os.IBinder remote) {mRemote = remote;}public android.os.IBinder asBinder() {return mRemote;}public java.lang.String getInterfaceDescriptor() {return DESCRIPTOR;}/*** 此方法運行在服務端。*/public android.os.IBinder queryBinder(int binderCode) throws android.os.RemoteException {//創建輸入類型Parcel對象android.os.Parcel _data = android.os.Parcel.obtain();//創建輸出類型Parcel對象android.os.Parcel _reply = android.os.Parcel.obtain();//創建返回值對象android.os.IBinder _result;try {_data.writeInterfaceToken(DESCRIPTOR);//寫入請求參數_data.writeInt(binderCode);//發起RPC(遠程過程調用請求),同時當前線程掛起;然后服務端的onTransact方法會被調用,直到RPC過程返回后,當前線程繼續執行mRemote.transact(Stub.TRANSACTION_queryBinder, _data, _reply, 0);_reply.readException();//從_reply中取出返回數據_result = _reply.readStrongBinder();} finally {_reply.recycle();_data.recycle();}return _result;}}static final int TRANSACTION_queryBinder = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);}public android.os.IBinder queryBinder(int binderCode) throws android.os.RemoteException; } 復制代碼?注意:首先當客戶端發起遠程請求時,由于當前線程會被掛起直至服務端進程返回數據,所有如果一個遠程方法是很耗時的,那么就不能在UI線程中發起此遠程請求;其次,由于服務端的Binder方法運行在Binder的線程池中,所以Binder方法不管是否耗時都應該采取同步的方式去實現,因為它已經運行在一個線程中了
2、Binder的使用
?上面實現AIDL文件的創建,那么如何使用尼?其實也比較簡單,創建一個Service,在其onBind里返回一個服務端Binder對象,在客戶端的ServiceConnection里拿到這個Binder對象。
//服務端 public class BinderPoolService extends Service {private static final String TAG = "BinderPoolService";private Binder mBinderPool = new BinderPool.BinderPoolImpl();public IBinder onBind(Intent intent) {return mBinderPool;} } //客戶端 private ServiceConnection mBinderPoolConnection = new ServiceConnection() {public void onServiceConnected(ComponentName name, IBinder service) {//拿到服務端返回的Binder接口mBinderPool = IBinderPool.Stub.asInterface(service);...}public void onServiceDisconnected(ComponentName name) {}}; Intent intent = new Intent(mContext, BinderPoolService.class); mContext.bindService(intent, mBinderPoolConnection, Context.BIND_AUTO_CREATE); 復制代碼3、Binder池的實現
?在《Android藝術探索》這本書中有一個Binder池概念,意思也就跟線程池、數據庫連接池概念一樣。避免為每個Binder都創建一個Service,在一個service里根據不同業務拿到其對應的Binder對象。這樣就能節省很多資源,畢竟Service也是系統資源。具體實現如下:
// public class BinderPoolService extends Service {private static final String TAG = "BinderPoolService";private Binder mBinderPool = new BinderPool.BinderPoolImpl();public IBinder onBind(Intent intent) {return mBinderPool;} } // public class BinderPool {private static final String TAG = "BinderPool";public static final int BINDER_NONE = -1;public static final int BINDER_COMPUTE = 0;public static final int BINDER_SECURITY_CENTER = 1;private Context mContext;private static volatile BinderPool mInstance;private CountDownLatch mConnectBinderPoolCountDownLatch;private IBinderPool mBinderPool;public BinderPool(Context context) {mContext = context.getApplicationContext();connectBinderPoolService();}public static BinderPool getInstance(Context context) {if (mInstance == null) {synchronized (BinderPool.class) {if (mInstance == null) {mInstance = new BinderPool(context);}}}return mInstance;}private synchronized void connectBinderPoolService() {mConnectBinderPoolCountDownLatch = new CountDownLatch(1);//開啟一個服務Intent intent = new Intent(mContext, BinderPoolService.class);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 ServiceConnection mBinderPoolConnection = new ServiceConnection() {public void onServiceConnected(ComponentName name, IBinder service) {//拿到服務端返回的Binder對象,mBinderPool = IBinderPool.Stub.asInterface(service);try {mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipient, 0);} catch (RemoteException e) {e.printStackTrace();}mConnectBinderPoolCountDownLatch.countDown();}public void onServiceDisconnected(ComponentName name) {}};private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() {public void binderDied() {Log.i(TAG, "binder die");//當斷開連接時回調的方法mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0);mBinderPool = null;//重新連接connectBinderPoolService();}};public static class BinderPoolImpl extends IBinderPool.Stub {public IBinder queryBinder(int binderCode) throws RemoteException {IBinder binder = null;//根據code返回不用的Binderswitch (binderCode) {case BINDER_SECURITY_CENTER:binder = new SecurityCenterImpl();break;case BINDER_COMPUTE:binder = new ComputeImpl();break;}return binder;}}} 復制代碼4、延伸
?延伸一:Binder是可能意外死亡的,這往往是由于服務端進程意外停止了,這時候就需要重新連接服務。那么如何監聽服務端是否死亡尼?有如下兩種方法:
- 給Binder設置DeathRecipient監聽,當Binder死亡后,會收到binderDied方法的回調,在binderDied里可以實現重連遠程服務。
- 在onServiceDisConnected中重連服務
?以上兩種方法可以任意選用,但是onServiceDisconnected是在UI線程中調用的,binderDied是在客戶端的Binder線程池中調用的。 ?延伸二:在平常開發中會經常使用觀察者這個設計模式,那么在多進程之間如何實現這種設計模式尼?很簡單,在服務端用一個集合來管理對象,然后來進行注冊與反注冊即可。代碼如下:
//CopyOnWriteArrayList是線程同步的 private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList = new CopyOnWriteArrayList<>();private Binder mBinder = new IBookManager.Stub() {...public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {mListenerList.add(listener);}public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {mListenerList.remove(listener);}}; 復制代碼 上面代碼無法實現觀察者?但是上面的實現對嗎?當測試的時候就會發現反注冊并沒有什么用。也就是上面的實現是錯誤的,為什么尼?因為Binder會把客戶端傳遞過來的對象重新轉化并生成一個新的對象,雖然注冊與反注冊過程中使用的是同一個客戶端對象,但是通過Binder傳遞到服務端后,卻會產生兩個全新的對象。由于對象是不能跨進程傳輸的,所以對象的跨進程傳輸本質上都是反序列化過程,這也就是AIDL中的自定義對象都必須要實現parcelable的原因。 ?那么該如何跨進程實現觀察者尼?可以用RemoteCallbackList這個集合。RemoteCallbackList是一個泛型,支持管理任意的AIDL接口,工作原理很簡單,在它的內部有一個Map結構專門用來保存所有的AIDL回調,這個Map的key是IBinder類型,value是Callback類型。當客戶端終止后,它能自動移除與該客戶端有關的對象,內部自動實現了線程同步的功能。那么就將上面的代碼中的CopyOnWriteArrayList換成RemoteCallbackList,代碼如下:
private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();private Binder mBinder = new IBookManager.Stub() {...public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {mListenerList.register(listener);}public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {mListenerList.unregister(listener);int N = mListenerList.beginBroadcast();mListenerList.finishBroadcast();}}; 復制代碼?這樣就實現了跨進程版的觀察者。在使用RemoteCallbackList時需要注意一點,無法像操作List一樣去操作它,盡管它的名字中帶有List,但它并不是一個List。遍歷RemoteCallbackList必須按照下面的方式進行,其中beginBroadcast與finishBroadcast必須配對使用,那么僅僅只是獲取RemoteCallbackList中的元素個數。
int N = mListenerList.beginBroadcast();Log.i(TAG, "unregisterListener current size: " + N);mListenerList.finishBroadcast(); 復制代碼總結
以上是生活随笔為你收集整理的Android Binder的使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Git 切换账号
- 下一篇: 转 Java的各种打包方式(JAR/WA