認識 LiveData
LiveData 是一種可觀察的數據存儲器類。同時它具備生命周期感知能力,確保只更新處于活躍生命周期狀態的觀察者組件。
LiveData 具有以下優勢:
- 確保界面符合數據狀態:當數據發生變化時,就會通知觀察者。觀察者監聽到有數據變化,就會更新界面。
- 不會發生內存泄漏,不需要手動解除觀察者:因為具備了生命周期感知能力,在生命周期宿主狀態變為 DESTROYED 后會自動移除觀察者。
- 不會因為生命周期宿主狀態不活躍而導致崩潰:當生命周期宿主處于非活躍狀態時,不會接收到任何 LiveData 事件。
- 數據始終保持最新狀態:當數據更新時,若生命周期宿主處于非活躍狀態,則不會接收到任何 LiveData 事件,在生命周期宿主變為活躍狀態時會接收最新數據。
使用 LiveData
- 添加對應的依賴:
livedata = { group = "androidx.lifecycle", name = "lifecycle-livedata", version = "2.8.3" }
如果項目使用了 Compose,記得添加以下依賴,針對 LiveData 提供了很多適用于 Compose 場景下的擴展方法:
androidx-runtime-livedata = { group = "androidx.compose.runtime", name = "runtime-livedata", version = "1.7.5" }
- 創建 LiveData 實例,指定源數據類型:LiveData 通常配合 ViewModel 使用,存儲在 ViewModel 對象中,通過 setValue()/postValue() 更新數據
class LiveDataViewModel : ViewModel() {
private val _data: MutableLiveData<Int> = MutableLiveData<Int>(0)
val data: LiveData<Int> get() = _data
fun updateValue() {
_data.value = (_data.value ?: 0) + 1
}
}
LiveData 可設置初始值,上面的例子中設置了初始值為0,如果沒有設置初始值,則初始為 null。
- 創建 Observer 實例:通過 observe 方法,傳入生命周期宿主和 Observer 實例,監聽數據變化,如果 LiveData 設置了初始值,則初始值也會調用一次 observe 方法
viewModel.data.observe(this) {
}
分析 LiveData 原理
LiveData 如何感知生命周期的變化?
對于 Android 中 Lifecycle 不熟悉的,可以先看這篇文章:Android Jetpact Lifecycle 解析。
LiveData的原理是觀察者模式,通過 LiveData#observe 方法監聽 LiveData 數據變化,需要傳入兩個參數,第一個參數為生命周期宿主 LifecycleOwner,第二個參數為 Observer 觀察者。
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
// 要求在主線程執行
assertMainThread("observe");
// 宿主生命周期已處于 DESTROYED 狀態,直接忽略
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
// ignore
return;
}
// 將 LifecycleOwner 和 Observer 包裝成 LifecycleBoundObserver
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
// 加入觀察者集合中
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
// 已經添加到觀察者集合中且綁定的 LifecycleOwner 不是傳進來的 owner
if (existing != null && !existing.isAttachedTo(owner)) {
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
// 已經添加到觀察者集合中且已經注冊owner
if (existing != null) {
return;
}
// 對 Lifecycle 注冊觀察者
owner.getLifecycle().addObserver(wrapper);
}
首先要求在主線程執行,如果宿主生命周期已處于 DESTROYED 狀態,直接忽略。然后將 LifecycleOwner 和 Observer 包裝成 LifecycleBoundObserver,這是最重要的一步,LifecycleBoundObserver 加入到了觀察者集合 mObservers 中,具備了觀察數據更新的能力,又對 LifecycleOwner 的 Lifecycle 注冊觀察,具備了生命周期感知的能力。
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
// 生命周期宿主
@NonNull
final LifecycleOwner mOwner;
LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
super(observer);
mOwner = owner;
}
@Override
boolean shouldBeActive() {
// Lifecycle 狀態大于等于 STARTED 時,認為是活躍狀態
return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
}
// 生命周期事件回調
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
// DESTROYED 狀態,移除觀察者,避免內存泄漏
if (currentState == DESTROYED) {
removeObserver(mObserver);
return;
}
Lifecycle.State prevState = null;
// 循環更新狀態,保證狀態是最新的
while (prevState != currentState) {
prevState = currentState;
// 修改活躍狀態,若為活躍狀態則進行數據分發
activeStateChanged(shouldBeActive());
currentState = mOwner.getLifecycle().getCurrentState();
}
}
@Override
boolean isAttachedTo(LifecycleOwner owner) {
return mOwner == owner;
}
@Override
void detachObserver() {
mOwner.getLifecycle().removeObserver(this);
}
}
LifecycleBoundObserver 是繼承 ObserverWrapper 的包裝類,實現了 LifecycleEventObserver 接口,可以收到 Lifecycle 生命周期事件的回調。
當 Lifecycle 生命周期變化時,回調 onStateChanged 方法,獲取到最新的生命周期狀態,若為 DESTROYED 狀態,移除觀察者,避免內存泄漏,否則通過循環更新狀態,保證狀態是最新的。
當 Lifecycle 狀態大于等于 STARTED 時,也就是為 STARTED 和 RESUMED 時,認為是活躍狀態,會進行數據分發。
@MainThread
public void observeForever(@NonNull Observer<? super T> observer) {
assertMainThread("observeForever");
AlwaysActiveObserver wrapper = new AlwaysActiveObserver(observer);
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
if (existing instanceof LiveData.LifecycleBoundObserver) {
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
if (existing != null) {
return;
}
wrapper.activeStateChanged(true);
}
如果想在數據更新的時候讓 Observer 立即得到通知,也就是說忽略生命周期狀態,可以使用 LiveData#observeForever 方法,不需要 LifecycleOwner,同時也就需要開發者手動 removeObserver。
observeForever 中將 Observer 包裝成 AlwaysActiveObserver,AlwaysActiveObserver 也是 ObserverWrapper 的實現類,其方法 shouldBeActive 固定返回 true,意味著只要有數據變化就會進行數據分發。
private class AlwaysActiveObserver extends ObserverWrapper {
AlwaysActiveObserver(Observer<? super T> observer) {
super(observer);
}
@Override
boolean shouldBeActive() {
return true;
}
}
LiveData 更新數據過程
LiveData 更新數據有兩種方式:setValue 和 postValue,setValue 只能用于主線程,postValue 可用于子線程。
@MainThread
protected void setValue(T value) {
// 只能在主線程
assertMainThread("setValue");
// 更新當前 LiveData 數據的版本號
mVersion++;
mData = value;
dispatchingValue(null);
}
mVersion 用于標記 LiveData 數據的版本,初始為-1,每更新一次數據,mVersion 加 1。
@SuppressWarnings("WeakerAccess") /* synthetic access */
void dispatchingValue(@Nullable ObserverWrapper initiator) {
if (mDispatchingValue) {
mDispatchInvalidated = true;
return;
}
mDispatchingValue = true;
do {
mDispatchInvalidated = false;
if (initiator != null) {
considerNotify(initiator);
initiator = null;
} else {
for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
considerNotify(iterator.next().getValue());
if (mDispatchInvalidated) {
//如果 mDispatchInvalidated 為 true,則中斷繼續遍歷過程
//用新值重新循環一遍
break;
}
}
}
} while (mDispatchInvalidated);
mDispatchingValue = false;
}
dispatchingValue 方法中,用兩個全局的布爾變量 mDispatchingValue 和 mDispatchInvalidated 實現了新舊值判斷、舊值舍棄、新值重新全局發布的邏輯。其中需要注意 mObservers 的遍歷過程,由于每遍歷一個 item 都會檢查一次當前的 value 是否已經過時,是的話則中斷遍歷,所以會存在僅有部分 Observer 收到了舊值的情況。
private void considerNotify(ObserverWrapper observer) {
//如果 observer 處于非活躍狀態,則直接返回
if (!observer.mActive) {
return;
}
// Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
//
// we still first check observer.active to keep it as the entrance for events. So even if
// the observer moved to an active state, if we've not received that event, we better not
// notify for a more predictable notification order.
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
//根據 observer 自己的 value 版本號 mLastVersion 來決定是否需要向其進行更新
//為了避免重復向某個 observer 更新
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
observer.mObserver.onChanged((T) mData);
}
postValue 方法不限定調用者所在線程,不管是主線程還是子線程都可以調用,因此是存在多線程競爭的可能性的。
protected void postValue(T value) {
boolean postTask;
synchronized (mDataLock) {
postTask = mPendingData == NOT_SET;
mPendingData = value;
}
if (!postTask) {
return;
}
ArchTaskExecutor.getInstance().postToMainThread(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 方法將更新的 value 值保存在變量 mPendingData 上,然后把 Runnable 對象 mPostValueRunnable 拋到主線程,其 run 方法中還是使用的 setValue 方法。由于最終更新值的操作仍然是在主線程,所以在 mPostValueRunnable 被執行前,所有通過 postValue 方法更新的 value 值都會被保存在變量 mPendingData 上,且只會保留最后一個,直到 mPostValueRunnable 被執行后 mPendingData 才會被重置,所以使用 postValue 方法在多線程同時調用或者單線程連續調用的情況下是存在丟值(外部的 Observer 只能接收到最新值)的可能性的。
Livedata 是否具有粘性,及原因
所謂粘性就是新的觀察者被老的數據值通知的現象,Livedata 是具有粘性的。
public abstract class LiveData<T> {
// 存儲數據的字段
private volatile Object mData;
// 數據的版本號
private int mVersion;
}
LiveData 的數據被存儲在內部的 mData 變量中,直到有更新的數據覆蓋,所以數據是持久的。LiveData 使用變量 mVersion 來標識數據的版本,用于判斷當前數據是否是最新的。
@MainThread
protected void setValue(T value) {
// 只能在主線程
assertMainThread("setValue");
// 更新當前 LiveData 數據的版本號
mVersion++;
mData = value;
dispatchingValue(null);
}
LiveData 有兩種場景會進行數據的分發:一種是通過 setValue 方法更新 LiveData 數據,postValue 方法最終也是通過 setValue 方法更新 LiveData 數據的,在 setValue 方法里通過 dispatchingValue 方法進行數據的分發,會遍歷所有觀察者并進行分發;另一種是新增觀察者或者觀察者的生命周期發生變化(至少為 STARTED),在觀察者 ObserverWrapper#activeStateChanged 方法里也是通過dispatchingValue 方法進行數據的分發,此時只會給單個觀察者進行數據分發。
private abstract class ObserverWrapper {
final Observer<? super T> mObserver;
boolean mActive;
int mLastVersion = START_VERSION;
ObserverWrapper(Observer<? super T> observer) {
mObserver = observer;
}
abstract boolean shouldBeActive();
boolean isAttachedTo(LifecycleOwner owner) {
return false;
}
void detachObserver() {
}
void activeStateChanged(boolean newActive) {
if (newActive == mActive) {
return;
}
// immediately set active state, so we'd never dispatch anything to inactive
// owner
mActive = newActive;
changeActiveCounter(mActive ? 1 : -1);
if (mActive) {
dispatchingValue(this);
}
}
}
private void considerNotify(ObserverWrapper observer) {
//如果 observer 處于非活躍狀態,則直接返回
if (!observer.mActive) {
return;
}
// Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
//
// we still first check observer.active to keep it as the entrance for events. So even if
// the observer moved to an active state, if we've not received that event, we better not
// notify for a more predictable notification order.
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
//根據 observer 自己的 value 版本號 mLastVersion 來決定是否需要向其進行更新
//為了避免重復向某個 observer 更新
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
observer.mObserver.onChanged((T) mData);
}
considerNotify 方法負責將數據分發給觀察者,會進行三次判斷,首先判斷數據觀察者是否活躍,然后判斷數據觀察者綁定的生命周期宿主是否活躍(因為可能生命周期宿主的活躍狀態還沒有同步到觀察者的活躍狀態),最后判斷數據觀察者的數據版本號是否是最新的,經過這三次判斷之后,會將最新的數據分發給觀察者。
因為新觀察者的數據版本號初始值為-1,必然小于 LiveData 的數據版本號,新觀察者被添加時會觸發一次數據數據分發,必然會接收到老的數據,所以 LiveData 是具有粘性的。
LiveData 的粘性問題,及解決方法
LiveData 的粘性可能會導致一些重復觸發的問題,比如常見的橫豎屏切換所導致的 Activity 的重建,Activity 內的觀察者也會被重新創建且添加,就會再次收到 LiveData 的數據,一些業務可能在此場景下會導致重復觸發的問題,有以下一些解決方法:
- 引入中間層記錄消費:在值的外面包裝一層,新增一個標記位標記是否被消費過,這樣就完全去除了 LiveData 的粘性特性。
// 一次性值
open class OneShotValue<out T>(private val value: T) {
// 值是否被消費
private var handled = false
// 獲取值,如果值未被處理則返回,否則返回空
fun getValue(): T? {
return if (handled) {
null
} else {
handled = true
value
}
}
// 獲取上次被處理的值
fun peekValue(): T = value
}
- 帶有最新版本號的觀察者:LiveData 數據分發給觀察者會進行三次判斷,其中一次就是數據版本號的判斷,如果觀察者在被創建添加時其數據版本號不是初始值-1,而是最新的數據版本號,則不會接收到老的數據。從外部修改觀察者的數據版本號需要通過反射修改。
- 使用 Kotlin Flow:SharedFlow 的構造參數中有一個參數 replay,表示重新發射給新的訂閱者的數據的數量,可以將舊的數據回播給新的訂閱者,默認為0,也就是說不會保留最后發送的數據,只會發送新狀態更新,設置為1,效果同 StateFlow 一樣,會保留最后發送的數據,也和 LiveData 一樣,變得具有粘性。