关于 AIDL 的介绍以及基本使用请参看上一篇,本篇主要记录 AIDL 使用中的一些注意事项以及一些高级用法。
使用监听
还是接着上一篇博客中的例子,如果想要在客户端被动的接收一些数据,即服务端数据发生变化后通知客户端——观察者模式。
下面就以增加了新书然后通知客户端作为例子来实现:
首先肯定要定义一个接口起名叫 IOnNewBookAddedListener.aidl;
|
|
然后再 IBookManager.aidl 中增加两个方法用来注册和取消注册;
|
|
然后改造服务端实现新增加的方法:
|
|
修改客户端如下:
|
|
以上就实现了一个简单的监听,每隔五秒会打印一条书籍的信息。
接下来就要说一些注意事项了,通过上面的代码可以发现在记录 IOnNewBookAddedListener 监听集合的时候使用了 RemoteCallbackList,而不是一般常用的 ArrayList 等为什么呢?
RemoteCallbackList
虽然常用的注册与解注册方法是传递相同的对象,而且上面也确实传递了同样的对象,但是首先要明白 AIDL 是用来实现多进程通讯的,其底层的 Binder 机制会把客户端传递过来的对象重新转化成一个新的对象,因此即使我们传递了相同的对象来解注册,但是到服务端的时候已经是一个新的对象了。这也是为什么使用自定义的对象的时候必须要实现 Parcelable 接口的原因。
RemoteCallbackList 是系统专门提供用于删除跨进程 listener 的接口,其内部维护了一个 Map 结构来保存所有的 AIDL 回调。
|
|
另外 RemoteCallbackList 在客户端进程终止后,会自动清除客户端注册的 listener,而且其内部实现了线程同步功能。
这里要注意的是遍历 RemoteCallbackList 的方法,只能使用下面的方法:
|
|
beginBroadcast() 和 finishBroadcast() 一定要配合使用。
注意 listener 回调所在线程
当服务端回调 listener 中的方法时,被回调的方法运行在客户端的 Binder 线程池中,所以当回调需要操作 UI 时记得将线程切换到 UI 线程,同时,如果客户端回调方法要执行大量耗时操作时,会阻塞服务端发起回调的方法,因此要确保当客户端回调方法执行耗时操作是,服务端发起回调的方法不要运行在 UI 线程中。就像例子中 BookManagerService 的 onBookAdded 方法,它调用了客户端的 IOnNewBookAddedListener 的 onNewBookAdded 方法,如果客户端的这个 onNewBookAdded 方法比较耗时的话,要确保 onBookAdded 方法运行在非 UI 线程中。
注意耗时操作
由于服务端方法本身运行在服务端的 Binder 线程池中,所以服务端的方法本身可以执行大量耗时操作,但是如果在客户端的 UI 线程中调用服务端的耗时方法的话就可能触法 ANR 异常,因为当客户端调用服务端方法时客户端的现成会被挂起直到服务端执行完毕。
所以当需要调用服务端的耗时方法时,客户端请务必放在子线程中操作。
死亡监听
可以有两个方法对 Service 的异常结束做监听。
给 Binder 添加 DeathRecipient 监听,当 Binder 死亡时会收到 binderDeid 回调
此方法运行在 Binder 线程池中。
在 onServiceDisconnected 中做监听,当 Service 异常结束时会调用此方法
此方法运行在 UI 线程中。
权限检查
- 可以通过自定义权限然后在 onBind 中进行权限验证;
- 通过自定义权限在 onTransact 中进行验证;
- 通过 Uid 和Pid 在 onTransact 中进行验证;
- 为 Service 制定 android:permission 属性来验证;
参考资料
- 《Android 开发艺术探索》-任玉刚
- Android 接口定义语言 (AIDL)