深刻源碼學習 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這個庫,並在最後進行總結和簡單的實踐分析。

相關文章
相關標籤/搜索