aidl相关文章
首先放出前两篇关于aidl的文章,没看过的可以看下。
前言
要实现服务端每增加一本书,会主动通知客户端。不用客户端一直去调用getBooks获取最新书,采用观察者模式。当服务端有新书到来时,会通知每一个已经注册监听的客户端,并把新书对象传给客户端。
具体实现
1.由于AIDL无法使用普通接口所以需要新建一个aidl接口INewBookArrivedListener.adil
,提供一个通知方法,注意导包。
package kanghb.com.aidltest;
import kanghb.com.aidltest.Book;
// Declare any non-default types here with import statements
interface INewBookArrivedListener {
void onNewBookArrived(in Book newBook);
}
2.在AidlBookManager.aidl中加入两个方法
interface AidlBookManager {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
//所有的返回值前都不需要加任何东西,不管是什么数据类型
List<Book> getBooks();
//传参时除了Java基本类型以及String,CharSequence之外的类型
//都需要在前面加上定向tag,具体加什么量需而定
void addBook(in Book book);
//新加两个方法
void registerListener(INewBookArrivedListener listener);
void unRegisterListener(INewBookArrivedListener listener);
}
3.点击makeProject(ctr+F9) 在build/generated/source/aidl/你的 flavor/
重新生成 下生成一下 Java 文件AidlBookManager.java,接下来AIDLService里必然会报错,因为在AidlBookManager新加了方法。
4.实现服务端代码,AIDLService里实现新加的两个方法。同时,让服务端没隔5秒新加一本书。
/**
CopyOnWriteArrayList支持并发读写,AIDL方法是在服务端的Binder线程池中执行的,因此当多个客户端同时连接的时候,会存在多个线程同时访问的情形,所以我们要在AIDL方法中处理线程同步,这里使用CopyOnWriteArrayList来进行自动的线程同步。因为AIDL中所支持的是抽象的List,而List只是一个接口,因此虽然服务端返回的是CopyOnWriteArrayList,但是在Binder中 会按照List的规范去访问数据并最终形成一个新的ArrayList传递给客户端,所以采用CopyOnWriteArrayList是可以的,类似的还有ConcurrentHashMap。
*/
private CopyOnWriteArrayList<INewBookArrivedListener> iNewBookArrivedListeners = new CopyOnWriteArrayList<>();
private AtomicBoolean isServiceDestroyed = new AtomicBoolean(false);
private final AidlBookManager.Stub bookManager = new AidlBookManager.Stub() {
// 。。。省略getBooks和addBook(Book book)那两个方法,主要看这两个方法
@Override
public void registerListener(INewBookArrivedListener listener) throws RemoteException {
if(!iNewBookArrivedListeners.contains(listener)){
iNewBookArrivedListeners.add(listener);
}else {
Log.e(TAG,"already exists.");
}
Log.e(TAG,"registerListener,size:" + iNewBookArrivedListeners.size());
}
@Override
public void unRegisterListener(INewBookArrivedListener listener) throws RemoteException {
if(iNewBookArrivedListeners.contains(listener)){
iNewBookArrivedListeners.remove(listener);
Log.e(TAG,"unregister listener succeed");
}else {
Log.e(TAG,"not found,unregister faild");
}
Log.e(TAG,"unregisterListener,size:" + iNewBookArrivedListeners.size());
}
}
@Override
public void onCreate() {
super.onCreate();
Book book = new Book();
book.setName("Android开发艺术探索");
book.setPrice(28);
books.add(book);
//没隔5秒新加一本书
new Thread(new ServiceRunnable()).start();
}
private class ServiceRunnable implements Runnable{
@Override
public void run() {
while (!isServiceDestroyed.get()){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Book book = new Book();
book.setName("服务端加新书啦" + books.size());
book.setPrice(books.size());
books.add(book);
for (int i = 0; i < iNewBookArrivedListeners.size(); i++) {
INewBookArrivedListener iNewBookArrivedListener = iNewBookArrivedListeners.get(i);
try {
iNewBookArrivedListener.onNewBookArrived(book);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
}
@Override
public void onDestroy() {
super.onDestroy();
isServiceDestroyed.set(true);
}
5.实现客户端代码(注意将aidl文件拷贝到客户端,因为这里客户端和服务端是两个app)
private INewBookArrivedListener iNewBookArrivedListener = new INewBookArrivedListener.Stub() {
@Override
public void onNewBookArrived(Book newBook) throws RemoteException {
//在客户端的Binder线程池中执行,所以要用handler切换到ui线程.
mHander.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook).sendToTarget();
}
};
public static Handler mHander = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case MESSAGE_NEW_BOOK_ARRIVED:
Log.e(TAG, "receive new book:" + msg.obj);
break;
default:
super.handleMessage(msg);
}
}
};
在onServiceConnected里注册和onDestroy中反注册
//注册INewBookArrivedListener到服务端
aidlBookManager.registerListener(iNewBookArrivedListener);
@Override
protected void onDestroy() {
super.onDestroy();
if (aidlBookManager != null && aidlBookManager.asBinder().isBinderAlive()) {
try {
//反注册
aidlBookManager.unRegisterListener(iNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
if (mBound) {
unbindService(serviceConnection);
mBound = false;
}
}
启动客户端和服务端运行看效果如下,客户端确实可以收到通知了。
但是,当客户端点击back结束mainActivity时,并没有成功取消注册。这是因为什么呢?
)
RemoteCallbackList
介绍
上述问题是因为在多进程中Binder会把传过来的对象重新转化为另外一个对象,虽然在客户端注册和反注册都使用同一个iNewBookArrivedListener
对象,但是Binder传送到服务端后会产生一个新的对象。RemoteCallbackList是
专门用来取消跨进程接口的,RemoteCallbackList
是一个泛型,支持任意AIDL接口。
public class RemoteCallbackList<E extends IInterface>
ArrayMap<IBinder, Callback> mCallbacks
= new ArrayMap<IBinder, Callback>();
内部有一个Map结构用来保存AIDL回调,Map的key是IBinder类型,value是Callback类型。当客户端注册iNewBookArrivedListener时,会把每个iNewBookArrivedListener的信息都存入mCallbacks中。
用法
1.替换iNewBookArrivedListeners的类型CopyOnWriteArrayList改为RemoteCallbackList
private RemoteCallbackList<INewBookArrivedListener> iNewBookArrivedListeners = new RemoteCallbackList<>();
// private CopyOnWriteArrayList<INewBookArrivedListener> iNewBookArrivedListeners = new CopyOnWriteArrayList<>();
2.修改registerListener和unRegisterListener方法的实现,变得很简单了。打印log时获取数量的方法也变了
@Override
public void registerListener(INewBookArrivedListener listener) throws RemoteException {
iNewBookArrivedListeners.register(listener);
// if(!iNewBookArrivedListeners.contains(listener)){
// iNewBookArrivedListeners.add(listener);
// }else {
// Log.e(TAG,"already exists.");
// }
Log.e(TAG,"registerListener,size:" + iNewBookArrivedListeners.getRegisteredCallbackCount());
}
@Override
public void unRegisterListener(INewBookArrivedListener listener) throws RemoteException {
iNewBookArrivedListeners.unregister(listener);
// if(iNewBookArrivedListeners.contains(listener)){
// iNewBookArrivedListeners.remove(listener);
// Log.e(TAG,"unregister listener succeed");
// }else {
// Log.e(TAG,"not found,unregister faild");
// }
Log.e(TAG,"unregisterListener,size:" + iNewBookArrivedListeners.getRegisteredCallbackCount());
}
3.更改通知客户端的方法,注意遍历RemoteCallBackList
,beginBroadcast
和finishBroadcast
方法必须配对使用。
// for (int i = 0; i < iNewBookArrivedListeners.size(); i++) {
// INewBookArrivedListener iNewBookArrivedListener = iNewBookArrivedListeners.get(i);
// try {
// iNewBookArrivedListener.onNewBookArrived(book);
// } catch (RemoteException e) {
// e.printStackTrace();
// }
// }
int n = iNewBookArrivedListeners.beginBroadcast();
for (int i = 0; i < n; i++) {
INewBookArrivedListener iNewBookArrivedListener = iNewBookArrivedListeners.getBroadcastItem(i);
if(iNewBookArrivedListener != null){
try {
iNewBookArrivedListener.onNewBookArrived(book);
} catch (RemoteException e) {
}
}
}
iNewBookArrivedListeners.finishBroadcast();
然后运行服务端app,客户端重新连接点击发送后,退出mainactivity看log,可以看出使用了RemoteCallBackList
后,的确可以完成跨进程的反注册功能。
来点干货
在aidl的学习记录2中我们讲过给BInder设置DeathRecipient
监听可以收到binderDied
()回调,然后重连远程服务。这样可以避免Binder意外死亡给客户端造成的影响。我们知道还有一种方法是在onServiceDisconnected
方法中重连远程服务。具体使用哪种可以二选一,那么这两者之间的区别是什么?
使用方法 | onServiceDisconnected() | binderDied() |
---|---|---|
运行线程 | 客户端UI线程 | 客户端Binder线程池 |
代码传送门:github