Jetpack MVVM七宗罪 之一:还在使用 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()

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
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

相信它会给大家带来很多收获:

img

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

  • 无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!!
  • 我希望每一个努力生活的IT工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。

当我们在抱怨环境,抱怨怀才不遇的时候,没有别的原因,一定是你做的还不够好!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

  • 无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!!
  • 我希望每一个努力生活的IT工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。

当我们在抱怨环境,抱怨怀才不遇的时候,没有别的原因,一定是你做的还不够好!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值