深刻源码学习 Android data binding 之:回调通知管理器 CallbackRegistry 解析

在android data binding库里面有三个版块我认为是掌握这个库的核心点,分别是:java

  • 注解定义和使用
  • 注解处理器的实现
  • 监听注册与回调

在前面的文章当中咱们已经分别分析了data binding当中的注解的使用和一个很关键的ViewDataBinding的类及apt编译期生成的相应子类的解析。若是你没看过的话能够先去看一下前面的文章。
深刻源码学习 android data binding 之:data binding 注解android

深刻源码学习 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)上同步的。若是一个线程持有某个管程对象上的锁,那么它就有权访问全部在该管程对象上同步的块。这就叫可重入。若线程已经持有锁,那么它就能够重复访问全部使用该锁的代码块。post

移除接口

上面说起到了,在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有一个很关键的特色—— 它是支持在通知发送过程当中不打断通知流程的可重入的修改咱们的回调接口集 。关于这一点,你们能够在细细的去看一下这个类的源码,慢慢体会。this

回调通知管理

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这个库,并在最后进行总结和简单的实践分析。

相关文章
相关标签/搜索