Android IPC机制—跨进程的观察者模式
在AIDL文件中并不是所有的数据类型都可以使用,AIDL支持的数据类型如下:
- 基本数据类型(int、long、char、boolean、double等)
- String和CharSequence
- List:只支持HashMap,HashMap的key和value的类型都必须被AIDL支持
- Parcelable:所有实现了Parcelable接口的对象
- AIDL:AIDL接口本身也可以在AIDL文件中使用
要注意的是,自定义的Parcelable对象和AIDL对象必须要显示import进来,不管他们是否和当前的AIDL文件位于同一包内。比如IBookManager.aidl这个文件里面用到了BookEntity这个类,BookEntity这个类实现了Parcelable接口,虽然他们位于同一包中但必须遵循AIDL规范,仍然要显式的import进来。另外,上一篇文中也提到过,如果AIDL文件中用到了自定义的Parcelable对象,那么必须新建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型。
AIDL中实现了Parcelable接口的类都需要按照上面的方式去创建相应的AIDL文件并声明它为Parcelable类型。除此之外,AIDL中除了基本数据类型,其他类型的参数必须表上in、out或者inout,in表示输入型参数,out表示输出型参数,inout表示输入输出参数。最后,AIDL接口中只支持方法,不支持声明静态常量。
为了方便AIDL的开发,建议把所有和AIDL相关的类和文件全部放在一个包中,这样做的好处是,当客户端是营外一个应用时,我们可以直接把整个包复制到客户端工程中。需要注意的是AIDL的包结构在服务端和客户端要保持一致,否则运行出错,这是因为客户端需要反序列化服务端中和AIDL接口相关的所有类,如果类的完整路径不一致的话,就无法成功反序列化,程序也就无法正常运行。
在示例中,服务端Service中采用了CopyOnRightArrayList,这个CopyOnRightArrayList支持并发读写,由于AIDL方法是在服务端的Binder线程池中执行的,因此当有多个客户端同时连接的时候会存在多个线程同时访问的情形,所以我们需要在AIDL方法中处理线程同步,这里使用CopyOnRightArrayList来进行自动的线程同步。
接下来在之前示例上继续完善,在客户端给服务端添加一本书然后再获取一次看看程序是否正常工作。客户端在服务连接后,在onServiceConnected中调用addBook(),再次查询会发现添加成功了。
现在考虑一种情况:用户不想时不时的去查询图书列表,于是他告诉图书管理人员"当有新书时把图书信息告诉我",这是一种典型的观察者模式。首先,我们需要提供一个AIDL接口,每个用户需要实现这个接口并且想图书馆申请新书的订阅功能,之所以选择AIDL接口而不是普通接口,是因为在AIDL中无法使用普通接口。新建一个IOnNewBookArrivedListener.aidl,我们所期望的是当Service中有新书到来时,就会通知每一个已经申请了通知的用户。
IOnNewBookArrivedListener.aidl:
IBookManager.aidl:
接下来,在服务端中也需要作修改,主要是Service中IBookManagre.Stub的实现,同时在BookManagerService中开启一个线程,每隔5秒向书库中增加一本书并通知所有订阅者。
服务端BookManagerService.java:
// CopyOnWriterArrayList支持并发读写,AIDL方法是在服务端的Binder线程池中执行的,因此多个客户端同时连接的 // 时候会存在多个线程同时访问的情形 private CopyOnWriteArrayList<BookEntity> mBookList = new CopyOnWriteArrayList<>(); private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList = new CopyOnWriteArrayList<>(); private AtomicBoolean isServiceDestoryed = new AtomicBoolean(false); private Binder mBinder = new IBookManager.Stub() { @Override public List<BookEntity> getBookList() throws RemoteException { return mBookList; } @Override public void addBook(BookEntity book) throws RemoteException { mBookList.add(book); } @Override public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException { if (!mListenerList.contains(listener)) { mListenerList.add(listener); } Logger.d("registerListener: " + mListenerList.size()); } @Override public void unRegisterListener(IOnNewBookArrivedListener listener) throws RemoteException { if (mListenerList.contains(listener)) { mListenerList.remove(listener); } Logger.d("unRegisterListener: " + mListenerList.size()); } }; @Override public void onCreate() { super.onCreate(); mBookList.add(new BookEntity("1", "Android从后门到入门")); mBookList.add(new BookEntity("2", "iOS从后门到前门")); new Thread(new AddBookWorker()).start(); } @Override public void onDestroy() { isServiceDestoryed.set(true); super.onDestroy(); } @Nullable @Override public IBinder onBind(Intent intent) { return mBinder; } private class AddBookWorker implements Runnable { @Override public void run() { while (!isServiceDestoryed.get()) { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } int bookId = mBookList.size() + 1; BookEntity bookEntity = new BookEntity(bookId + "", "new Book#" + bookId); try { onNewBookArrived(bookEntity); } catch (RemoteException e) { e.printStackTrace(); } } } } private void onNewBookArrived(BookEntity bookEntity) throws RemoteException { mBookList.add(bookEntity); for (int i = 0; i < mListenerList.size(); i++) { IOnNewBookArrivedListener listener = mListenerList.get(i); listener.onNewBookArrived(bookEntity); } }
客户端BookManagerActivity.java:
public class BookManagerActivity extends AppCompatActivity { private static final int NEW_BOOK_ARRIVED = 1; // 客户端需要注册IOnNewBookArrivedListener到远程服务端,当有新书时服务端才能通知客户端 // 在Activity退出时解除注册 private IBookManager mRemoteBookManager; private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == NEW_BOOK_ARRIVED) { Logger.d("new book arrived: " + msg.obj); } } }; private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { // 绑定成功后将Binder对象转换成AIDL接口 IBookManager bookManager = IBookManager.Stub.asInterface(service); try { mRemoteBookManager = bookManager; BookEntity bookEntity = new BookEntity("3", "Android AIDL"); bookManager.addBook(bookEntity); List<BookEntity> list = bookManager.getBookList(); for (BookEntity book : list) { Logger.d(book.getBookId() + "," + book.getBookName()); } bookManager.registerListener(mNewBookArrivedListener); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { mRemoteBookManager = null; } }; private IOnNewBookArrivedListener mNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() { @Override public void onNewBookArrived(BookEntity newBook) throws RemoteException { mHandler.obtainMessage(NEW_BOOK_ARRIVED, newBook.getBookId()).sendToTarget(); } }; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = new Intent(this, BookManagerService.class); bindService(intent, mServiceConnection, BIND_AUTO_CREATE); } @Override protected void onDestroy() { if (mRemoteBookManager != null && mRemoteBookManager.asBinder().isBinderAlive()) { try { mRemoteBookManager.unRegisterListener(mNewBookArrivedListener); } catch (RemoteException e) { e.printStackTrace(); } } unbindService(mServiceConnection); super.onDestroy(); } }
特别注意的是,打印Log测试时一定注意切换进程查看日志。
到这里对AIDL也有了一个初步的认识,但是AIDL还远远不止这些,通过文字总结再结合代码慢慢琢磨一定能慢慢理解AIDL这种实现方式。