Jetpack MVVM 常見錯(cuò)誤一:使用 Fragment 作為 LifecycleOwner

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);
        }
    }

然后在 onCreateViewonDestroyView 時(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)期待。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容