
Fragment 作為 LifecycleOwner 的問題
MVVM 的核心是數(shù)據(jù)驅(qū)動(dòng)UI,在 Jetpack 中,這一思想體現(xiàn)在以下場(chǎng)景:Fragment 通過訂閱 ViewModel 中的 LiveData 以驅(qū)動(dòng)自身 UI 的更新
關(guān)于訂閱的時(shí)機(jī),一般會(huì)選擇放到 onViewCreated 中進(jìn)行,如下:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.liveData.observe(this) { // Warning : Use fragment as the LifecycleOwner
updateUI(it)
}
}
我們知道訂閱 LiveData 時(shí)需要傳入 LifecycleOwner 以防止泄露,此時(shí)一個(gè)容易犯的錯(cuò)誤是使用 Fragment 作為這個(gè) LifecycleOwner,某些場(chǎng)景下會(huì)造成重復(fù)訂閱的Bug。
做個(gè)實(shí)驗(yàn)如下:
val handler = Handler(Looper.getMainLooper())
class MyFragment1 : Fragment() {
val data = MutableLiveData<Int>()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
tv.setOnClickListener {
parentFragmentManager.beginTransaction()
.replace(R.id.container, MyFragment2())
.addToBackStack(null)
.commit()
handler.post{ data.value = 1 }
}
data.observe(this, Observer {
Log.e("fragment", "count: ${data.value}")
})
}
當(dāng)跳轉(zhuǎn)到 MyFragment2 然后再返回 MyFragment1 中時(shí),會(huì)打出輸出兩條log
E/fragment: count: 1
E/fragment: count: 1
原因分析
LiveData 之所以能夠防止泄露,是當(dāng) LifecycleOwner 生命周期走到 DESTROYED 的時(shí)候會(huì) remove 調(diào)其關(guān)聯(lián)的 Observer
//LiveData.java
@Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
removeObserver(mObserver);
return;
}
activeStateChanged(shouldBeActive());
}
前面例子中,基于 FragmentManager#replace 的頁(yè)面跳轉(zhuǎn),使得 MyFragment1 發(fā)生了從 BackStack 的出棧/入棧,由于 Framgent 實(shí)例被復(fù)用并沒有發(fā)生 onDestroy, 但是 Fragment的 View 的重建導(dǎo)致重新 onCreateView, 這使得 Observer 被 add 了兩次,但是沒有對(duì)應(yīng)的 remove。
所以歸其原因, 是由于 Fragment 的 Lifecycle 與 Fragment#mView 的 Lifecycle 不一致導(dǎo)致我們訂閱 LiveData 的時(shí)機(jī)和所使用的 LivecycleOwner 不匹配,所以在任何基于 replace 進(jìn)行頁(yè)面切換的場(chǎng)景中,例如 ViewPager、Navigation 等會(huì)發(fā)生上述bug

解決方法
明白了問題原因,解決思路也就清楚了:必須要保證訂閱的時(shí)機(jī)和所使用的LifecycleOwner相匹配,即要么調(diào)整訂閱時(shí)機(jī),要么修改LifecycleOwner
在 onCreate 中訂閱
思路一是修改訂閱時(shí)機(jī),講訂閱提前到 onCreate, 可以保證與 onDestory 的成對(duì)出現(xiàn),但不幸的是這會(huì)帶來(lái)另一個(gè)問題。
當(dāng) Fragment 出入棧造成 View 重建時(shí),我們需要重建后的 View 也能顯示最新狀態(tài)。但是由于 onCreate 中的訂閱的 Observer 已經(jīng)獲取過 LiveData 的最新的 Value,如果 Value 沒有新的變化是無(wú)法再次通知 Obsever 的

在 LiveData 源碼中體現(xiàn)在通知 Obsever 之前對(duì) mLastVersion 的判斷:
//LiveData.java
private void considerNotify(ObserverWrapper observer) {
if (!observer.mActive) {
return;
}
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
if (observer.mLastVersion >= mVersion) {// Value已經(jīng)處于最新的version
return;
}
observer.mLastVersion = mVersion;
//noinspection unchecked
observer.mObserver.onChanged((T) mData);
}
正是為了保證重建后的 View 也能刷新最新的數(shù)據(jù), 我們才在 onViewCreated 中完成訂閱。因此只能考慮另一個(gè)思路,替換 LifecycleOwner
使用 ViewLifecycleOwner
Support-28 或 AndroidX-1.0.0 起,F(xiàn)ragment 新增了 getViewLifecycleOwner 方法。顧名思義,它返回一個(gè)與 Fragment#mView 向匹配的 LifecycleOwner,可以在 onDestroyView 的時(shí)候走到 DESTROYED ,刪除 onCreateView 中注冊(cè)的 Observer, 保證了 add/remove 的成對(duì)出現(xiàn)。

看一下源碼,原理非常簡(jiǎn)單
//Fragment.java
void performCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
//...
mViewLifecycleOwner = new LifecycleOwner() {
@Override
public Lifecycle getLifecycle() {
if (mViewLifecycleRegistry == null) {
mViewLifecycleRegistry = new LifecycleRegistry(mViewLifecycleOwner);
}
return mViewLifecycleRegistry;
}
};
mViewLifecycleRegistry = null;
mView = onCreateView(inflater, container, savedInstanceState);
if (mView != null) {
// Initialize the LifecycleRegistry if needed
mViewLifecycleOwner.getLifecycle();
// Then inform any Observers of the new LifecycleOwner
mViewLifecycleOwnerLiveData.setValue(mViewLifecycleOwner); //mViewLifecycleOwnerLiveData在后文介紹
} else {
//...
}
}
基于 mViewLifecycleRegistry 創(chuàng)建 mViewLifecycleOwner,
@CallSuper
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {// called when onCreateView
if (mView != null) {
mViewLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
}
}
@CallSuper
public void onDestroyView() {
if (mView != null) {
mViewLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
}
}
然后在 onCreateView 和 onDestroyView 時(shí),推進(jìn)到合適的生命周期。
getViewLifecycleOwnerLiveData
順道提一下,與 getViewLifecycleOwner 同時(shí)新增的還有 getViewLifecycleOwnerLiveData。 從前面貼的源碼中對(duì) mViewLifecycleOwnerLiveData 的使用,應(yīng)該可以猜出它的作用: 它是前文討論的思路1的實(shí)現(xiàn)方案,即使在 onCreate 中訂閱,由于在 onCreateView 中對(duì) LiveData 進(jìn)行了重新設(shè)置,所以重建后的 View 也可以更新數(shù)據(jù)。
// Then inform any Observers of the new LifecycleOwner
mViewLifecycleOwnerLiveData.setValue(mViewLifecycleOwner);
需要特別注意的是,根據(jù) MVVM 最佳實(shí)踐,我們希望由 ViewModel 而不是 Fragment 持有 LiveData,所以不再推薦使用 getViewLifecycleOwnerLiveData
最后: StateFlow 與 lifecycleScope
前面都是以 LiveData 為例介紹對(duì) ViewLifecycleOwner 的使用, 如今大家也越來(lái)越多的開始使用協(xié)程的 StateFlow , 同樣要注意不要錯(cuò)用 LifecycleOwner
訂閱 StateFlow 需要 CoroutineScope, AndroidX 提供了基于 LifecycleOwner 的擴(kuò)展方法
val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
get() = lifecycle.coroutineScope
當(dāng)我們?cè)?Fragment 中獲取 lifecycleScope 時(shí),切記要使用 ViewLifecycleOwner
class MyFragment : Fragment() {
val viewModel: MyViewModel by viewModel()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//使用 viewLifecycleOwner 的 lifecycleScope
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.someDataFlow.collect {
updateUI(it)
}
}
}
}
}
注意此處出現(xiàn)了一個(gè) repeatOnLifecycle(...), 這跟本文無(wú)關(guān),但是將涉及到第二宗罪的劇情,敬請(qǐng)期待。