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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM