Android多程序之Binder解綁監聽的問題
阿新 • • 發佈:2018-12-13
Android多程序系列
接上一篇文章《Android多程序之手動編寫Binder類》中向服務端註冊監聽事件的問題,在擴充套件了Binder類後,我們還需要改造對應的服務端和客戶端
客戶端和服務端的改造
服務端改造
- 增加註冊監聽介面的功能
private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList = new CopyOnWriteArrayList<IOnNewBookArrivedListener>(); private Binder mBinder = new BookManagerImpl(){ @Override public List<Book> getBookList() throws RemoteException { Log.e(TAG, "getBookList-->"+ System.currentTimeMillis()); return mBookList; } @Override public void addBook(Book book) throws RemoteException { Log.e(TAG, "addBook-->"); mBookList.add(book); } @Override public void registerListener(IOnNewBookArrivedListener listener) { if (!mListenerList.contains(listener)) { mListenerList.add(listener); }else { Log.e(TAG, "already exists"); } Log.e(TAG, "registerListener, size:"+mListenerList.size()); } @Override public void unRegisterListener(IOnNewBookArrivedListener listener) { if (mListenerList.contains(listener)) { mListenerList.remove(listener); Log.e(TAG, "unRegisterListener listener succeed"); }else { Log.e(TAG, "not found, can not unregister"); } Log.e(TAG, "unRegisterListener, current size:"+mListenerList.size()); } };
- 新增一個任務,定時像書籍列表中新增一本書,並觸發通知客戶端的操作
@Override public void onCreate() { super.onCreate(); Log.e(TAG, "onCreate-->"+ System.currentTimeMillis()); mBookList.add(new Book(1, "Android")); mBookList.add(new Book(2, "IOS")); new Thread(new ServiceWorker()).start(); } private void onNewBookArrived(Book book) throws RemoteException{ mBookList.add(book); Log.e(TAG, "new book arrived, notify listeners:" + mListenerList.size()); for (int i=0; i<mListenerList.size(); i++) { IOnNewBookArrivedListener listener = mListenerList.get(i); Log.e(TAG, "new book arrived, notify listener:" + listener); listener.onNewBookArrived(book); } } private class ServiceWorker implements Runnable { @Override public void run() { while (!mIsServiceDestoryed.get()) { try { Thread.sleep(5000); }catch (InterruptedException e) { e.printStackTrace(); } int bookId = mBookList.size() + 1; Book newBook = new Book(bookId, "new book#" + bookId); try { onNewBookArrived(newBook); }catch (RemoteException e) { e.printStackTrace(); } } } }
- 通知客戶端的過程就是呼叫每個註冊的監聽介面的onNewBookArrived方法,也就是一個服務端呼叫客戶端的過程,分別是呼叫onNewBookArrived–>onTransact–>Proxy的onNewBookArrived–>客戶端
客戶端改造
- 首先要宣告一個IOnNewBookArrivedListener物件,並註冊到服務端中
private IBookManager mRemoteBookManager; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { Log.e(TAG, "ServiceConnection-->"+ System.currentTimeMillis()); IBookManager bookManager = BookManagerImpl.asInterface(iBinder); mRemoteBookManager = bookManager; try { List<Book> list = bookManager.getBookList(); Log.e(TAG, "query book list, list type:" + list.getClass().getCanonicalName()); Log.e(TAG, "query book list:" + list.toString()); Book newBook = new Book(3, "Android 進階"); bookManager.addBook(newBook); Log.e(TAG, "add book:" + newBook); List<Book> newList = bookManager.getBookList(); Log.e(TAG, "query book list:" + newList.toString()); bookManager.registerListener(mOnNewBookArrivedListener); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName componentName) { mRemoteBookManager = null; Log.e(TAG, "binder died"); } }; /** * 這個方法執行在客戶端的binder執行緒池中,不能直接進行UI操作 */ private IOnNewBookArrivedListener mOnNewBookArrivedListener = new OnNewBookArrivedListenerImpl(){ @Override public void onNewBookArrived(Book book) { mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, book).sendToTarget(); } };
- 然後由於客戶端的IOnNewBookArrivedListener回撥方法onNewBookArrived執行在Binder執行緒池中,所以需要一個Handler來切換到UI執行緒
private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message message) {
switch (message.what) {
case MESSAGE_NEW_BOOK_ARRIVED:
Log.e(TAG, "receive new book:" + message.obj);
break;
default:
super.handleMessage(message);
}
}
};
- 最後需要在客戶端退出時解除服務端的註冊監聽
@Override
protected void onDestroy() {
if (mRemoteBookManager != null && mRemoteBookManager.asBinder().isBinderAlive()) {
try {
Log.e(TAG, "unRegister listener:" + mOnNewBookArrivedListener);
mRemoteBookManager.unRegisterListener(mOnNewBookArrivedListener);
}catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(mConnection);
super.onDestroy();
}
改造結果
-
通過上圖我們可以看到,我們已經成功實現了預期的功能,並且服務端通知客戶端的呼叫過程也如我們上面所說的那樣
-
接下去我們退出應用,這樣可以測試解綁監聽的功能
-
從上圖我們可以看到,服務端呼叫解綁失敗了,提示找不到介面,這是咋回事呢?
利用Binder進行程序間通訊,Binder會把客戶端傳遞的引數AIDL介面和Parcelable物件,重新轉化並生成一個新的物件。因為物件是不能跨程序傳輸的,物件的跨程序傳輸本質上就是序列化和反序列化的過程。所以上述情況服務端根本就沒有客戶端的那個物件,那肯定找不到會解綁失敗。那咋辦呢?
RemoteCallBackList
RemoteCallBackList是啥
public class RemoteCallbackList<E extends IInterface> {
/*package*/ ArrayMap<IBinder, Callback> mCallbacks
= new ArrayMap<IBinder, Callback>();
...
}
- RemoteCallBackList的內部有一個Map結構用來儲存所有的AIDL回撥,這個Map的key是IBinder型別,value是CallBack型別
public boolean register(E callback, Object cookie) {
synchronized (mCallbacks) {
if (mKilled) {
return false;
}
IBinder binder = callback.asBinder();
try {
Callback cb = new Callback(callback, cookie);
binder.linkToDeath(cb, 0);
mCallbacks.put(binder, cb);
return true;
} catch (RemoteException e) {
return false;
}
}
}
- 每次有新的介面來,就呼叫register方法註冊,新增到mCallbacks中
- 通過上面的原始碼我們可以看到,由於客戶端跨程序傳輸的物件的底層的Binder物件都是同一個,所以我們可以通過這一點,當需要解除註冊監聽時,就可以通過遍歷RemoteCallBackList找到與解綁客戶端Binder物件相同的listener並刪除即可
- 而且我們可以看到RemoteCallBackList中實現了執行緒同步,我們在利用它進行註冊和解註冊時不需要處理同步問題。
- 特別的是,客戶端程序終止後,RemoteCallBackList能夠自動解除客戶端所註冊的listener
用RemoteCallBackList實現實現解綁註冊
private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<IOnNewBookArrivedListener>();
private Binder mBinder = new BookManagerImpl(){
@Override
public List<Book> getBookList() throws RemoteException {
Log.e(TAG, "getBookList-->"+ System.currentTimeMillis());
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
Log.e(TAG, "addBook-->");
mBookList.add(book);
}
@Override
public void registerListener(IOnNewBookArrivedListener listener) {
//註冊介面
mListenerList.register(listener);
}
@Override
public void unRegisterListener(IOnNewBookArrivedListener listener) {
//解註冊介面
mListenerList.unregister(listener);
}
};
//通知客戶端
private void onNewBookArrived(Book book) throws RemoteException{
mBookList.add(book);
final int N = mListenerList.beginBroadcast();
for (int i=0; i<N; i++) {
IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
if (listener != null) {
try {
listener.onNewBookArrived(book);
}catch (RemoteException e) {
e.printStackTrace();
}
}
}
mListenerList.finishBroadcast();
}
- 通過RemoteCallBackList的改造,我們就可以成功解註冊客戶端的listener了
注意點
- RemoteCallBackList並不是一個List,我們無法像操作list一樣操作它,比如呼叫size方法
- 遍歷RemoteCallBackList必須按照上面程式碼中的方式進行,beginBroadcast方法和finishBroadcast方法必須配對使用
歡迎關注我的微信公眾號,和我一起學習一起成長!