1. 程式人生 > 其它 >Android面試:說一下 LiveData 的 postValue ?與SetValue有什麼區別?連續呼叫會有什麼問題?為什麼?

Android面試:說一下 LiveData 的 postValue ?與SetValue有什麼區別?連續呼叫會有什麼問題?為什麼?

眾所周知,程式設計師面試的時候,很多面試官喜歡會就一個問題不斷深入追問。

例如一個小小的 LiveData 的 postValue,就可能會問出一連串問題:

postValue 與 setValue

postValuesetValue 一樣都是用來更新 LiveData 資料的方法:

  • setValue 只能在主執行緒呼叫,同步更新資料
  • postValue 可在後臺執行緒呼叫,其內部會切換到主執行緒呼叫 setValue
liveData.postValue("a");
liveData.setValue("b");

上面程式碼,a 在 b 之後才被更新。

postValue 收不到通知

postValue 使用不當,可能發生接收到資料變更的通知:

If you called this method multiple times before a main thread executed a posted task, only the last value would be dispatched.

如上,原始碼的註釋中明確記載了,當連續呼叫 postValue 時,有可能只會收到最後一次資料更新通知。

梳理原始碼可以瞭解其中原由:

protected void postValue(T value) {
    boolean postTask;
    synchronized (mDataLock) {
        postTask = mPendingData == NOT_SET;
        mPendingData = value;
    }
    if (!postTask) {
        return;
    }
    ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}

mPendingData 被成功賦值 value 後,post 了一個 Runnable

mPostValueRunnable 的實現如下:

private final Runnable mPostValueRunnable = new Runnable() {
    @SuppressWarnings("unchecked")
    @Override
    public void run() {
        Object newValue;
        synchronized (mDataLock) {
            newValue = mPendingData;
            mPendingData = NOT_SET;
        }
        setValue((T) newValue);
    }
};
  • postValue 將資料存入 mPendingDatamPostValueRunnable 在UI執行緒消費mPendingData

  • 在 Runnable 中 mPendingData 值還沒有被消費之前,即使連續 postValue , 也不會 post 新的 Runnable

  • mPendingData 的生產 (賦值) 和消費(賦 NOT_SET) 需要加鎖

這也就是當連續 postValue 時只會收到最後一次通知的原因。

原始碼梳理過了,但是為什麼要這樣設計呢?

為什麼 Runnable 只 post 一次?

mPenddingData 中有資料不斷更新時,為什麼 Runnable 不是每次都 post,而是等待到最後只 post 一次?

一種理解是為了兼顧效能,UI只需顯示最終狀態即可,省略中間態造成的頻發重新整理。這或許是設計目的之一,但是一個更為合理的解釋是:即使 post 多次也沒有意義,所以只 post 一次即可

我們知道,對於 setValue 來說,連續呼叫多次,資料會依次更新:

如下,訂閱方一次收到 a b 的通知

liveData.setValue("a");
liveData.setValue("b");

通過原始碼可知,dispatchingValue() 中同步呼叫 Observer#onChanged(),依次通知訂閱方:

//setValue原始碼

@MainThread
protected void setValue(T value) {
    assertMainThread("setValue");
    mVersion++;
    mData = value;
    dispatchingValue(null);
}

但對於 postValue,如果當 value 變化時,我們立即post,而不進行阻塞

protected void postValue(T value) {
    mPendingData = value;
    ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}

private final Runnable mPostValueRunnable = new Runnable() {
    public void run() {
        setValue((T) mPendingData);
    }
};
liveData.postValue("a")
liveData.postValue("b")

由於執行緒切換的開銷,連續呼叫 postValue,收到通知只能是b、b,無法收到a。

因此,post 多次已無意義,一次即可。

為什麼要加讀寫鎖?

前面已經知道,是否 post 取決於對 mPendingData 的判斷(是否為 NOT_SET)。因為要在多執行緒環境中訪問 mPendingData ,不加讀寫鎖無法保證其執行緒安全。

protected void postValue(T value) {
    boolean postTask = mPendingData == NOT_SET; // --1
    mPendingData = value; // --2
    if (!postTask) {
        return;
    }
    ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}

private final Runnable mPostValueRunnable = new Runnable() {
    public void run() {
        Object newValue = mPendingData;
        mPendingData = NOT_SET; // --3
        setValue((T) newValue);
    }
};

如上,如果在 1 和 2 之間,執行了 3,則 2 中設定的值將無法得到更新

使用RxJava替換LiveData

如何避免在多執行緒環境下不漏掉任何一個通知? 比較好的思路是藉助 RxJava 這樣的流式框架,任何資料更新都以資料流的形式發射出來,這樣就不會丟失了。

fun <T> Observable<T>.toLiveData(): LiveData<T> = RxLiveData(this)

class RxLiveData<T>(
    private val observable: Observable<T>
) : LiveData<T>() {
    private var disposable: Disposable? = null

    override fun onActive() {
        disposable = observable
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe({
                setValue(it)
            }, {
                setValue(null)
            })
    }

    override fun onInactive() {
        disposable?.dispose()
    }
}

最後

想要保證事件線上程切換過程中的順序性和完整性,需要使用RxJava這樣的流式框架。

有時候面試官會使用追問的形式來挖掘候選人的技術深度,所以大家在準備面試時要多問自己幾個問什麼,知其然並知其所以然。

當然,我也不贊同這種刨根問底式的拷問方式,尤其是揪著一些沒有實用價值的細枝末節不放。所以本文也是提醒廣大面試官,挖掘深度的同時要注意分寸,不能以將候選人難倒為目標來問問題。

好了,今天的文章就到這裡,感謝閱讀,喜歡的話不要忘了三連。大家的支援和認可,是我分享的最大動力。

Android高階開發系統進階筆記、最新面試複習筆記PDF,我的GitHub

原文首發:https://juejin.cn/post/6971608728042733605