在上一篇博客 Android 笔记:IPC - AIDL 中讲到的 IPC 方式之一 AIDL 的基本用法,本文主要记录一下 AIDL 的进阶。
AIDL 中的注册与反注册 之前的 IBookManager
中只提供了获取 书籍列表(getBookList) 和 添加书籍(addBook) 的方法,现在我们将其扩展一下,希望能加入“当新的书本被添加之后,能够通知出去”。这是一种 观察者模式
:
由于 AIDL
中无法使用普通接口,所以添加新的 IOnNewBookArrived.aidl
接口,用于回调新书的到来:
1 2 3 4 5 6 7 package com.github.tianma8023.ipclearn.aidl;import com.github.tianma8023.ipclearn.aidl.Book;interface IOnNewBookArrivedListener { void onNewBookArrived (in Book newBook) ; }
IBookManager.aidl
接口中添加新的注册监听/取消监听的方法:
1 2 3 4 5 6 7 8 9 10 11 12 package com.github.tianma8023.ipclearn.aidl;import com.github.tianma8023.ipclearn.aidl.Book;import com.github.tianma8023.ipclearn.aidl.IOnNewBookArrivedListener;interface IBookManager { List<Book> getBookList () ; void addBook (in Book book) ; void registerListener (IOnNewBookArrivedListener listener) ; void unregisterListener (IOnNewBookArrivedListener listener) ; }
远程服务端实现 BookManagerService
, 实现新加的两个方法,在 Service 中开启线程,定时加入一本新书通知所有注册者:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 public class BookManagerService extends Service { private static final String TAG = "BMS" ; private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false ); private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>(); private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList = new CopyOnWriteArrayList<>(); private Binder mBinder = new IBookManager.Stub() { @Override public List<Book> getBookList () throws RemoteException { return mBookList; } @Override public void addBook (Book book) throws RemoteException { mBookList.add(book); } @Override public void registerListener (IOnNewBookArrivedListener listener) throws RemoteException { if (!mListenerList.contains(listener)) { mListenerList.add(listener); } else { Log.d(TAG, "listener already exists " + listener); } } @Override public void unregisterListener (IOnNewBookArrivedListener) throws RemoteException { if (mListenerList.contains(listener)) { mListenerList.remove(listener); Log.d(TAG, "unregister listener succeed." ); } else { Log.d(TAG, "listener not found, unregiser failed" ); } } }; @Override public IBinder onBind (Intent intent) { return mBinder; } @Override public void onCreate () { super .onCreate(); mBookList.add(new Book(1 , "Think in Java" )); mBookList.add(new Book(2 , "Android Programming" )); new Thread(new ServiceWorker).start(); } @Override public void onDestroy () { mIsServiceDestroyed.set(true ); super .onDestroy(); } private void onNewBookArrived (Book book) throws RemoteException { mBookList.add(book); Log.d(TAG, "new book arrived, notify all listeners" ); for (IOnNewBookArrivedListener listener : mListenerList) { listener.onNewBookArrived(book); } } private class ServiceWorker implements Runnable { @Override public void run () { while (!mIsServiceDestroyed.get()) { try { TimeUnit.SECONDS.sleep(5 ); } catch (InterruptedException e) { e.printStackTrace(); } int bookId = mBookList.size() + 1 ; Book newBook = new Book(bookId, "new book#" + bookId); try { onNewBookArrived(newBook); } catch (RemoteExcption e) { e.printStackTrace(); } } } } }
BookManagerServicce
置于独立进程中:
1 2 3 <service android:name =".aidl.BookManagerService" android:process =":remote" />
客户端 BookManagerActivity
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 public class BookManagerActivity extends Activity { private static final String TAG = "BookManagerActivity" ; private static final int MSG_NEW_BOOK_ARRIVED = 1 ; private IBookManager mRemoteBookManager; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected (ComponentName name, IBinder service) { IBookManager bookManager = IBookManager.Stub.asInterface(service); mRemoteBookManager = bookManager; try { List<Book> bookList = bookManager.getBookList(); Log.i(TAG, "book list type: " + bookList.getClass().getCanonicalName()); Log.i(TAG, "query book list: " + bookList); bookManager.registerListener(mOnNewBookArrivedListener); } catch (RemoteException e) { e.printStackTrace(); } } public void onServiceDisconnected (ComponentName name) { mRemoteBookManager = null ; } }; private Handler mHandler = new Handler(new Handler.Callback() { @Override public boolean handleMessage (Message msg) { switch (msg.what) { case MSG_NEW_BOOK_ARRIVED: String message = "received new book: " + msg.obj; Log.d(TAG, message); break ; } return true ; } }); private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() { @Override public void onNewBookArrived (Book newBook) throws RemoteException { mHandler.obtainMessage(MSG_NEW_BOOK_ARRIVED, newBook).sendToTarget(); } }; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_book_manager); Intent intent = new Intent(this , BookManagerService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onDestroy () { preDestroy(); super .onDestroy(); } private void preDestroy () { if (mRemoteBookManager != null && mRemoteManager.asBinder().isBinderAlive()) { try { Log.i(TAG, "unregister listener: " + mOnNewBookArrivedListener); mRemoteBookManager.unregisterListener(mOnNewBookArrivedListener); } catch (RemoteException e) { e.printStackTrace(); } } unbindService(mConnection); } }
log日志:
1 2 3 4 5 com.github.tianma8023.ipclearn:remote I/BMS: new book arrived, notify all listeners com.github.tianma8023.ipclearn D/BookManagerActivity: received new book: Book{bookId=3, bookName='new book#3'} com.github.tianma8023.ipclearn I/BookManagerActivity: unregister listener: com.github.tianma8023.ipclearn.aidl.BookManagerActivity$4@80e21c1 com.github.tianma8023.ipclearn:remote D/BMS: listener not found, unregister failed
通过日志,可以看出,服务端每次的书籍推送确实收到,但是在服务端执行 unregisterListener
的时候却并没有正确执行 。问题的原因在于, Binder
会把客户端传递过来的对象重新转化成新的对象,在客户端注册/反注册过程中使用的是同一个客户端对象,但是通过 Binder
传递到服务端的对象则不一样,所以会存在反注册失败的现象。
上述问题的解决方案就是使用 RemoteCallbackList
。RemoteCallbackList
由系统提供,专门用来删除跨进成的 listener 接口。
1 2 3 public class RemoteCallbackList <E extends IInterface >public interface IOnNewBookArrivedListener extends android .os .IInterface
可以看出来,RemoteCallbackList
是一个泛型,可以管理任意的 AIDL 接口。
RemoteCallbackList
部分源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 public class RemoteCallbackList <E extends IInterface > { ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>(); private final class Callback implements IBinder .DeathRecipent { final E mCallback; final Object mCookie; Callback(E callback, Object cookie) { mCallback = callback; mCookie = cookie; } } }
可以看出来 RemoteCallbackList
内部其实是一个 Map
,建立起 IBinder
到 Callback
的映射,而 Callback
又封装了真正的 IInterface
的 listener
,也就是实际上建立起了 IBinder
到 listener
的映射。
虽然之前的 IOnNewBookArrivedListener
会由客户端的一个对象会在服务端生成不一样的对象,但是其底层的 Binder
是一样的,所以 RemoteCallbackListener
正是利用了这个特性,真正地实现反注册。
正确姿势: 用 RemoteCallbackList
替换前面的 CopyOnWriteArrayList
:
1 2 private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();
修改 registerListener
和 unregisterListener
:
1 2 3 4 5 6 7 8 9 @Override public void registerListener (IOnNewBookArrivedListener listener) throws RemoteException { mListenerList.register(listener); } @Override public void unregisterListener (IOnNewBookArrivedListener listener) throws RemoteException { mListenerList.unregister(listener); }
修改通知所有已注册的 listener 的 onNewBookArrived
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 private void onNewBookArrived (Book book) throws RemoteException { mBookList.add(book); Log.i(TAG, "new book arrived, notify all listeners" ); final int N = mListenerList.beginBroadcast(); for (int i = 0 ; i < N; i++) { IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i); if (listener != null ) { listener.onNewBookArrived(book); } } mListenerList.finishBroadcast(); }
注意,遍历 RemoteCallbackList
必须要用上述方式进行,即 begiinBroadcast
和 finishBroadcast
必须配对使用。
AIDL 重连 Service 当服务端程序意外停止时,Binder
会意外死亡,需要重新连接 Service
,有两种解决方案:
给 Binder
设置 DeathRecipient
监听,当 Binder
死亡时,会回调 DeathRecipient#binderDied()
,这时可以重连 Service;
在 ServiceConnection#onServiceDisconnected()
中重连 Service。
两者的区别在于在 bindDied
中是在客户端的 Binder
线程池被调用,而 onServiceDisconnected
是在UI线程中被回调。
BookManagerActivity
中加入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public class BookManagerActivity extends Activity { private IBookManager mRemoteBookManager; private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied () { if (mRemoteBookManager == null ) return ; mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient, 0 ); mRemoteBookManager = null ; } } private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected (ComponentName name, IBinder service) { IBookManager bookManager = IBookManager.Stub.asInterface(service); mRemoteBookManager = bookManager; try { mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient, 0 ); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected (ComponentName name) { mRemoteBookManager = null ; } } }
权限验证 默认情况下,远程服务可以被其他外部组件连接上,所以,必要的时候需要加入权限验证。通常有以下几种方案:
在远程 Service 的 onBind 中进行验证,这里的验证方式也有很多,常用的是使用 permission
进行验证: 在 AndroidManifest 中声明所需要的权限:
1 2 3 <permission android:name ="com.github.tianma8023.ipclearn.permission.ACCESS_BOOK_SERVICE" android:level ="normal" />
在 BookManagerService
的 onBind
中做权限验证:
1 2 3 4 5 6 7 public IBinder onBind (Intent intent) { int result = checkCallingOrSelfPermission (com.github.tianma8023.ipclearn.permission.ACCESS_BOOK_SERVICE); if (result == PackageManager.PERMISSION_DENIED) return null ; return mBinder; }
这样就完成了权限的验证,当有客户端需要连接该服务时,需要申请权限:
1 2 <uses-permission android:name ="com.github.tianma8023.ipclearn.permission.ACCESS_BOOK_SERVICE" />
在服务端的 Binder#onTransact()
中进行权限验证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 private Binder mBinder = new IBookManager.Stub() { @Override public boolean onTransact (int code, Parcel data, Parcel reply, int flags) throws RemoteException { int check = checkCallingOrSelfPermission("com.github.tianma8023.ipclearn.permission.ACCESS_BOOK_SERVICE" ); if (check == PackageManager.PERMISSION_DENIED) return false ; String packageName = null ; String[] packages = getPackageManager().getPackagesForUid(getCallingUid()); if (packages != null && packages.length > 0 ) { packageName = packages[0 ]; } if (packageName == null || !packageName.startsWith("com.github.tianma8023" )) { return false ; } return super .onTransact(code, data, reply, flags); } }
上述代码片段,验证了 permission,还验证了包名前缀,只有都满足要求的才能验证通过。
为 Service
指定 android:permission
属性。
代码参考 IPCLearn