首先承认这个系列有点标题党,Jetpack 的 MVVM 本身没有错,错在开发者的某些使用不当。本系列将分享那些 AAC 中常见的错误用法,指导大家打造更健康的应用架构
Fragment 作为 LifecycleOwner 的问题
MVVM 的核心是数据驱动UI,在 Jetpack 中,这一思想体现在以下场景:Fragment 通过订阅 ViewModel 中的 LiveData 以驱动自身 UI 的更新
关于订阅的时机,一般会选择放到 onViewCreated
中进行,如下:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewModel.liveData.observe(this) { // Warning : Use fragment as the LifecycleOwner updateUI(it) } }
我们知道订阅 LiveData 时需要传入 LifecycleOwner 以防止泄露,此时一个容易犯的错误是使用 Fragment 作为这个 LifecycleOwner,某些场景下会造成重复订阅的Bug。
做个实验如下:
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}") }) }
当跳转到 MyFragment2 然后再返回 MyFragment1 中时,会打出输出两条log
E/fragment: count: 1 E/fragment: count: 1
原因分析
LiveData 之所以能够防止泄露,是当 LifecycleOwner 生命周期走到 DESTROYED
的时候会 remove 调其关联的 Observer
//LiveData.java @Override public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) { if (mOwner.getLifecycle().getCurrentState() == DESTROYED) { removeObserver(mObserver); return; } activeStateChanged(shouldBeActive()); }
前面例子中,基于 FragmentManager#replace
的页面跳转,使得 MyFragment1 发生了从 BackStack 的出栈/入栈,由于 Framgent 实例被复用并没有发生 onDestroy
, 但是 Fragment的 View 的重建导致重新 onCreateView
, 这使得 Observer 被 add 了两次,但是没有对应的 remove。
所以归其原因, 是由于 Fragment 的 Lifecycle 与 Fragment#mView 的 Lifecycle 不一致导致我们订阅 LiveData 的时机和所使用的 LivecycleOwner 不匹配,所以在任何基于 replace 进行页面切换的场景中,例如 ViewPager、Navigation 等会发生上述bug
解决方法
明白了问题原因,解决思路也就清楚了:必须要保证订阅的时机和所使用的LifecycleOwner相匹配,即要么调整订阅时机,要么修改LifecycleOwner
在 onCreate 中订阅
思路一是修改订阅时机,讲订阅提前到 onCreate
, 可以保证与 onDestory
的成对出现,但不幸的是这会带来另一个问题。
当 Fragment 出入栈造成 View 重建时,我们需要重建后的 View 也能显示最新状态。但是由于 onCreate 中的订阅的 Observer 已经获取过 LiveData 的最新的 Value,如果 Value 没有新的变化是无法再次通知 Obsever 的
在 LiveData 源码中体现在通知 Obsever
之前对 mLastVersion
的判断:
//LiveData.java private void considerNotify(ObserverWrapper observer) { if (!observer.mActive) { return; } if (!observer.shouldBeActive()) { observer.activeStateChanged(false); return; } if (observer.mLastVersion >= mVersion) {// Value已经处于最新的version return; } observer.mLastVersion = mVersion; //noinspection unchecked observer.mObserver.onChanged((T) mData); }
正是为了保证重建后的 View 也能刷新最新的数据, 我们才在 onViewCreated
中完成订阅。因此只能考虑另一个思路,替换 LifecycleOwner
使用 ViewLifecycleOwner
Support-28 或 AndroidX-1.0.0 起,Fragment 新增了 getViewLifecycleOwner
方法。顾名思义,它返回一个与 Fragment#mView
向匹配的 LifecycleOwner,可以在 onDestroyView
的时候走到 DESTROYED
,删除 onCreateView
中注册的 Observer, 保证了 add/remove 的成对出现。
看一下源码,原理非常简单
//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
创建 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
时,推进到合适的生命周期。
getViewLifecycleOwnerLiveData
顺道提一下,与 getViewLifecycleOwner
同时新增的还有 getViewLifecycleOwnerLiveData
。 从前面贴的源码中对 mViewLifecycleOwnerLiveData 的使用,应该可以猜出它的作用: 它是前文讨论的思路1的实现方案,即使在 onCreate
中订阅,由于在 onCreateView
中对 LiveData 进行了重新设置,所以重建后的 View 也可以更新数据。
// Then inform any Observers of the new LifecycleOwner mViewLifecycleOwnerLiveData.setValue(mViewLifecycleOwner);
需要特别注意的是,根据 MVVM 最佳实践,我们希望由 ViewModel 而不是 Fragment 持有 LiveData,所以不再推荐使用 getViewLifecycleOwnerLiveData
最后: StateFlow 与 lifecycleScope
前面都是以 LiveData
为例介绍对 ViewLifecycleOwner 的使用, 如今大家也越来越多的开始使用协程的 StateFlow
, 同样要注意不要错用 LifecycleOwner
订阅 StateFlow 需要 CoroutineScope
, AndroidX 提供了基于 LifecycleOwner 的扩展方法
val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope get() = lifecycle.coroutineScope
当我们在 Fragment 中获取 lifecycleScope
时,切记要使用 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) } } } } }
注意此处出现了一个 repeatOnLifecycle(...)
, 这跟本文无关,但是将涉及到第二宗罪的剧情,敬请期待。