对于RemoteCallbackList
的使用,本博客不再介绍。网上很多博客都有相关的描述。本文主要是对我们为什么要这样或者那样去使用RemoteCallbackList
做一个分析,学习一下源码时怎么封装的
首先带着几个问题去学习会更加深刻:
- 为什么
beginBroadcast
和finishBroadcast
要成对使用?
- 为什么我们使用
RemoteCallbackList
中的unregister
能移除监听,我们自己写的普通的ArrayList存放监听回调后,移除没有效果?
- 怎么更好的使用
RemoteCallbackList
,有哪些需要注意的地方吗?
问题 1
为什么我们使用RemoteCallbackList
中的unregister
能移除监听,我们自己写的普通的ArrayList存放监听回调后,移除没有效果,就需要去看RemoteCallbackList
是怎么管理监听回调的
首先看一下里面定义的全局变量:
1 2 3 4 5
| ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>(); private Object[] mActiveBroadcast; private int mBroadcastCount = -1; private boolean mKilled = false; private StringBuilder mRecentCallers;
|
下面就是我们使用的register
方法,可以选择传一或两个参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public boolean register(E callback) { return register(callback, null); }
public boolean register(E callback, Object cookie) { synchronized (mCallbacks) { if (mKilled) { return false; } logExcessiveCallbacks(); IBinder binder = callback.asBinder(); try { Callback cb = new Callback(callback, cookie); unregister(callback); binder.linkToDeath(cb, 0); mCallbacks.put(binder, cb); return true; } catch (RemoteException e) { return false; } } }
|
这里一个需要关注的点是Callback
类,下面的注释很清晰
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| private final class Callback implements IBinder.DeathRecipient { final E mCallback; final Object mCookie;
Callback(E callback, Object cookie) { mCallback = callback; mCookie = cookie; } public void binderDied() { synchronized (mCallbacks) { mCallbacks.remove(mCallback.asBinder()); } onCallbackDied(mCallback, mCookie); } }
|
unregister
的实现逻辑:
1 2 3 4 5 6 7 8 9 10
| public boolean unregister(E callback) { synchronized (mCallbacks) { Callback cb = mCallbacks.remove(callback.asBinder()); if (cb != null) { cb.mCallback.asBinder().unlinkToDeath(cb, 0); return true; } return false; }
|
在客户端调用unregister方法,如果我们是使用普通的ArrayList
存放,大概率会使用list.remove(listener)
,当然这种方式并不能移除监听,思考一个问题:客户端传过来的listener还是原来的listener对象吗?IPC通信是有一个序列化的过程的,序列化前和序列化后的对象并不是同一个对象。
那么RemoteCallbackList
是怎么解决这个问题的呢?是Binder对象。IBinder binder = callback.asBinder();
虽然回调对象都是新生成的,但是他们底层的binder对象还是同一个,利用这个特性,RemoteCallbackList
再自身维护的ArrayMap中移除我们指定的对象。
当然可以看到存放进ArrayMap中的每一个对象里都注册了死亡监听,当Binder失效的时候,会帮我们自动移除listener。
问题 2
为什么beginBroadcast
和finishBroadcast
要成对使用?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public int beginBroadcast() { synchronized (mCallbacks) { if (mBroadcastCount > 0) { throw new IllegalStateException( "beginBroadcast() called while already in a broadcast"); } final int N = mBroadcastCount = mCallbacks.size(); if (N <= 0) { return 0; } Object[] active = mActiveBroadcast; if (active == null || active.length < N) { mActiveBroadcast = active = new Object[N]; } for (int i=0; i<N; i++) { active[i] = mCallbacks.valueAt(i); } return N;
|
可以看到在开始广播后,将所有的回调都存放进了一个数组,我们最后是去取数组中的listener对象进行通知,这样的原因是:
数组的访问速度比 ArrayMap 更快,特别是在频繁访问的情况下。这里通过将 ArrayMap 中的值复制到数组中,可以减少在广播过程中对 ArrayMap 的多次访问,提高性能。
其次在我们开始广播后,就确定了要通知的listener对象,避免在多线程环境下对 ArrayMap 的并发修改问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public void finishBroadcast() { synchronized (mCallbacks) { if (mBroadcastCount < 0) { throw new IllegalStateException( "finishBroadcast() called outside of a broadcast"); } Object[] active = mActiveBroadcast; if (active != null) { final int N = mBroadcastCount; for (int i=0; i<N; i++) { active[i] = null; } } mBroadcastCount = -1; } }
|
看了上面的源码,也就明白为什么要成对使用beginBroadcast
和finishBroadcast
了。为了确保线程安全,当确定要IPC通信时,会把要通知的对象放到数组里面,那么当我们通信结束,自然也得清空数组里面的数据。
问题 3
怎么更好的使用RemoteCallbackList
,有哪些需要注意的地方吗?
总结以下几点:
- 可以继承
RemoteCallbackList
,比如实现onCallbackDied
方法,处理Binder死亡情况进一步处理。
- 一次只能有一对
beginBroadcast
和finishBroadcast
,所以要避免通知调用,或者加锁
- register(E callback, Object cookie) 可以携带一些关键信息,比如客户端的包名,方便我们定位