在android data binding库里面有三个版块我认为是掌握这个库的核心点,分别是:
- 注解定义和使用
- 注解处理器的实现
- 监听注册与回调
在前面的文章当中我们已经分别分析了data binding当中的注解的使用和一个很关键的ViewDataBinding的类及apt编译期生成的相应子类的解析。如果你没看过的话可以先去看一下前面的文章。
深入源码学习 android data binding 之:data binding 注解
深入源码学习 android data binding 之:ViewDataBinding
而 CallbackRegistry 这个类就是掌控监听注册与回调的一个核心点。在data binding的变量setter方法和rebind的过程中都是通过CallbackRegistry作为核心逻辑的部分。从类的定义注释我们可以知道,这其实一个工具类,负责存储和通知我们的回调接口。并且这个类本身就包括了一个常规的逻辑——在接收到通知之后取消这个回调的注册。接下来,我会根据个人的理解对这个类的设计和API进行简单的介绍。
类设计简述
在以前我写接口回调的代码的时候通常都是进行"简单定义回调接口、接口依赖注入、回调处理"这样一个流程的,所以代码结构会比较零散。而 CallbackRegistry 生来就是一个管理者,管理接口的注册和回调。它把接口的定义和接口回调之后的处理逻辑交给调用者去实现,而本身只负责管理这个接口以及完成回调分发的逻辑。我们先节选一部分源码进行说明。
/**
* 一个CallbackRegistry的实例用于管理一种类型的回调接口
* @param <C> 对应的是我们的回调接口类型
* @param <T> 通知发送者的类型,一般就是CallbackRegistry实例所在的类
* @param <A> 用于接口回调时额外的参数
*/
public class CallbackRegistry<C, T, A> implements Cloneable {
...
// 通过list管理我们所有注册的回调
private List<C> mCallbacks = new ArrayList<C>();
...
// 当我们在构造一个CallbackRegistry的实例的时候,我们需要传入一个NotifierCallback对象
// 这个对象就是用于掌控具体接口回调之后的逻辑处理的
// 这是一个抽象类,由调用方自主实现,类定义可以看下面
public CallbackRegistry(NotifierCallback<C, T, A> notifier) {
mNotifier = notifier;
}
...
// 泛型参数的定义跟上面相同
public abstract static class NotifierCallback<C, T, A> {
/**
* 当我们调用CallbackRegistry#notifyCallbacks(Object, int, Object)的方法的时候最终会回调这个方法
* @param callback The callback to notify.
* @param sender The opaque sender object.
* @param arg The opaque notification parameter.
* @param arg2 An opaque argument passed in
* {@link CallbackRegistry#notifyCallbacks}
* @see CallbackRegistry#CallbackRegistry(CallbackRegistry.NotifierCallback)
*/
public abstract void onNotifyCallback(C callback, T sender, int arg, A arg2);
}
}复制代码
回调接口管理
位标记回调接口的状态
在CallbackRegistry类当中,我们可以不断的添加回调接口,当我们要移除接口的时候,并不是直接把回调从list集合中移除,而是判断当前通知是否正在发送。如果通知正在发送,那么会通过long类型里面的每一个位标志接口的取消状态。这样子可以避免并发修改list带来的线程安全的问题。如下所示:
/**
* A bit flag for the first 64 listeners that are removed during notification.
* The lowest significant bit corresponds to the 0th index into mCallbacks.
* For a small number of callbacks, no additional array of objects needs to
* be allocated.
*/
private long mFirst64Removed = 0x0;
/**
* Bit flags for the remaining callbacks that are removed during notification.
* When there are more than 64 callbacks and one is marked for removal, a dynamic
* array of bits are allocated for the callbacks.
*/
private long[] mRemainderRemoved;复制代码
默认的情况下,CallbackRegistry类只会使用一个long类型标志接口的状态(是否被移除),一个long值可以标记64个接口的状态。在接口数超出64个之后会使用一个动态的long类型的数组mRemainderRemoved负责处理。
// 设置移除接口回调的标志位
// index对应的是回调接口在list中的位置
private void setRemovalBit(int index) {
if (index < Long.SIZE) {
// It is in the first 64 callbacks, just check the bit.
// 通过位移运算更新位的值
final long bitMask = 1L << index;
mFirst64Removed |= bitMask;
} else {
final int remainderIndex = (index / Long.SIZE) - 1;
if (mRemainderRemoved == null) {
mRemainderRemoved = new long[mCallbacks.size() / Long.SIZE];
} else if (mRemainderRemoved.length < remainderIndex) {
// need to make it bigger
// 动态的调整数组的大小
long[] newRemainders = new long[mCallbacks.size() / Long.SIZE];
System.arraycopy(mRemainderRemoved, 0, newRemainders, 0, mRemainderRemoved.length);
mRemainderRemoved = newRemainders;
}
final long bitMask = 1L << (index % Long.SIZE);
mRemainderRemoved[remainderIndex] |= bitMask;
}
}复制代码
添加接口
/**
* 当我们需要添加的接口已经存在的时候,不会重复添加
* 要注意的是,在CallbackRegistry里面,开放的都是实例的synchronized方法
* 而这在Java中这相当于 synchronized(this)块,而这是可重入的锁。
* 而CallbackRegistry在通知回调的时候又通过了long类型的位来处理,所以添加新的回调并不会影响当前的通知
* @param callback The callback to add.
*/
public synchronized void add(C callback) {
if (callback == null) {
throw new IllegalArgumentException("callback cannot be null");
}
int index = mCallbacks.lastIndexOf(callback);
if (index < 0 || isRemoved(index)) {
mCallbacks.add(callback);
}
}
/**
* Returns true if the callback at index has been marked for removal.
*
* @param index The index into mCallbacks to check.
* @return true if the callback at index has been marked for removal.
*/
private boolean isRemoved(int index) {
if (index < Long.SIZE) {
// It is in the first 64 callbacks, just check the bit.
final long bitMask = 1L << index;
return (mFirst64Removed & bitMask) != 0;
} else if (mRemainderRemoved == null) {
// It is after the first 64 callbacks, but nothing else was marked for removal.
return false;
} else {
final int maskIndex = (index / Long.SIZE) - 1;
if (maskIndex >= mRemainderRemoved.length) {
// There are some items in mRemainderRemoved, but nothing at the given index.
return false;
} else {
// There is something marked for removal, so we have to check the bit.
final long bits = mRemainderRemoved[maskIndex];
final long bitMask = 1L << (index % Long.SIZE);
return (bits & bitMask) != 0;
}
}
}复制代码
关于可重入锁的概念,这里稍微提及一下:对于带有synchronized关键字的实例方法,在Java中这相当于 synchronized(this)块,因此这些方法都是在同一个管程对象(即this)上同步的。如果一个线程持有某个管程对象上的锁,那么它就有权访问所有在该管程对象上同步的块。这就叫可重入。若线程已经持有锁,那么它就可以重复访问所有使用该锁的代码块。
移除接口
上面提及到了,在CallbackRegistry中,回调的移除并不是立即从list中直接将对象删除的,而是通过位标志来管理状态的。
/**
* 移除回调
* 当通知正在发送的时候,不会将接口移除,而只是标记移除的状态
* 在通知发送完毕之后再将回调接口从list当中移除
* @param callback The callback to remove.
*/
public synchronized void remove(C callback) {
//mNotificationLevel是一个成员变量,这个变量会在每次通知发送前+1,通知发送完毕之后又-1
//所以当mNotificationLevel不为0的时候,表明通知正在发送中
if (mNotificationLevel == 0) {
mCallbacks.remove(callback);
} else {
int index = mCallbacks.lastIndexOf(callback);
if (index >= 0) {
setRemovalBit(index);
}
}
}复制代码
清空容器
/**
* Removes all callbacks from the list.
*/
public synchronized void clear() {
if (mNotificationLevel == 0) {
mCallbacks.clear();
} else if (!mCallbacks.isEmpty()) {
for (int i = mCallbacks.size() - 1; i >= 0; i--) {
setRemovalBit(i);
}
}
}复制代码
通过上面的几处分析,我们会发现,CallbackRegistry这个类灵活的运用了位状态和synchronized关键字来处理了并发状态下的list容器的管理。这时得CallbackRegistry有一个很关键的特点—— 它是支持在通知发送过程中不打断通知流程的可重入的修改我们的回调接口集 。关于这一点,大家可以在细细的去看一下这个类的源码,慢慢体会。
回调通知管理
CallbackRegistry的回调通知有一个很显著的特点,那就是使用递归算法分发通知。
/**
* Notify all callbacks.
* 通知所有的回调,这个是通知回调的入口,最终会通过调用NotifierCallback#onNotifyCallback()方法调用自己实现的具体逻辑
* @param sender The originator. This is an opaque parameter passed to
* {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, Object)}
* @param arg An opaque parameter passed to
* {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, Object)}
* @param arg2 An opaque parameter passed to
* {@link CallbackRegistry.NotifierCallback#onNotifyCallback(Object, Object, int, Object)}
*/
public synchronized void notifyCallbacks(T sender, int arg, A arg2) {
// 通过一个int值标志通知发送的层级,每次发送通知之前都会加1
mNotificationLevel++;
// 这个方法通过递归算法去完成回调通知
notifyRecurse(sender, arg, arg2);
mNotificationLevel--;
// 当所有的通知分发完毕之后,将之前标记的需要移除的接口从容器中移除
if (mNotificationLevel == 0) {
if (mRemainderRemoved != null) {
for (int i = mRemainderRemoved.length - 1; i >= 0; i--) {
final long removedBits = mRemainderRemoved[i];
if (removedBits != 0) {
removeRemovedCallbacks((i + 1) * Long.SIZE, removedBits);
mRemainderRemoved[i] = 0;
}
}
}
if (mFirst64Removed != 0) {
removeRemovedCallbacks(0, mFirst64Removed);
mFirst64Removed = 0;
}
}
}
/**
* 下面主要就是两个方法的调用,并且两个方法最终都会发起通知回调,这里需要解释一下为什么会这样子做
* 在类里面我们是通过long类型的每一个bit标志对应的接口的移除状态的,当我们的接口超过64个之后就会通过继续增加long数组来继续标志
* 但是有可能我们的接口数是超过64的,但是并没有做过移除的操作,所以并不会新建long数组去记录标志位信息
* 也就是我们的接口跟标志位结合来看会存在两种情况,一种是最大标记位之前的接口,一种是最大标志位之后没有被标记的接口.
* 所以notifyRemainder()方法通知的是那些从开始到存在的最大标识位之前的接口
* notifyCallbacks()方法通知的是最大标志位之后到接口总数之间的接口
* 如果上面我的表述看不明白的话可以看下面的图片
*/
private void notifyRecurse(T sender, int arg, A arg2) {
final int callbackCount = mCallbacks.size();
final int remainderIndex = mRemainderRemoved == null ? -1 : mRemainderRemoved.length - 1;
// Now we've got all callbakcs that have no mRemainderRemoved value, so notify the others.
notifyRemainder(sender, arg, arg2, remainderIndex);
// notifyRemainder notifies all at maxIndex, so we'd normally start at maxIndex + 1
// However, we must also keep track of those in mFirst64Removed, so we add 2 instead:
final int startCallbackIndex = (remainderIndex + 2) * Long.SIZE;
// The remaining have no bit set
notifyCallbacks(sender, arg, arg2, startCallbackIndex, callbackCount, 0);
}
// 下面这里就是我们非常熟悉的递归算法了
private void notifyRemainder(T sender, int arg, A arg2, int remainderIndex) {
if (remainderIndex < 0) {
notifyFirst64(sender, arg, arg2);
} else {
final long bits = mRemainderRemoved[remainderIndex];
final int startIndex = (remainderIndex + 1) * Long.SIZE;
final int endIndex = Math.min(mCallbacks.size(), startIndex + Long.SIZE);
notifyRemainder(sender, arg, arg2, remainderIndex - 1);
notifyCallbacks(sender, arg, arg2, startIndex, endIndex, bits);
}
}
/**
* 从startIndex到endIndex循环发起通知回调
* bits用以标记每一个位对应的接口是否已经被移除。当bits是0的时候表示所有的通知都需要通知
*/
private void notifyCallbacks(T sender, int arg, A arg2, final int startIndex,final int endIndex, final long bits) {
// 从到一个bit开始,通过位与操作判断bits对应的位是0还是1(1表示移除标志)
// 每一轮之后bitMask左移一位,用此判断每个位对应的状态
long bitMask = 1;
for (int i = startIndex; i < endIndex; i++) {
if ((bits & bitMask) == 0) {
mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2);
}
bitMask <<= 1;
}
}
private void notifyFirst64(T sender, int arg, A arg2) {
final int maxNotified = Math.min(Long.SIZE, mCallbacks.size());
notifyCallbacks(sender, arg, arg2, 0, maxNotified, mFirst64Removed);
}复制代码
如果仔细看了上面的源码以及注释的话应该能够明白整个回调通知流程是怎么走的了,最终都会调用 NotifierCallback#onNotifyCallback() 方法,这就是由我们在使用CallbackRegistry的时候必须创建并传入的NotifierCallback对象。
关于CallbackRegistry的实际使用,我们在前面的ViewDataBinding的分析文章里面已经提及到,这里不再过多陈述。
感谢你宝贵的时间阅读这篇文章,如果你喜欢的话可以点赞收藏,也可以关注我的账号。我的个人主页浅唱android也会更新我的文章。接下来我还会继续分析android data binding这个库,并在最后进行总结和简单的实践分析。