我最近注意到一个案例,在一个 Android 应用程序中,触发了以下异常:
java.lang.IllegalArgumentException: Cannot add the same observer with different lifecycles
在片段的
onViewCreated
回调(比如片段A)中为视图模型的实时数据注册一些观察者时发生:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
...
myViewModel.myLiveData.observe(viewLifecycleOwner, Observer(::onValueChanged))
...
}
这让我想到这发生在一种特定情况下,当在活动的
onCreate
中收到意图并决定导航到另一个片段(比如片段B)时。
override fun onCreate(savedInstanceState: Bundle?) {
if ("myAction" == intent?.action) {
if (R.id.fragmentA == navController.currentDestination?.id) {
navController.navigate(FragmentADirections.actionFragmentAToFragmentB())
}
}
}
如果用户从片段 B 导航回片段 A,则抛出
IllegalArgumentException
。
在记录生命周期方面发生的事情后,我意识到片段 A 的
viewLifecycleOwner
的生命周期状态是INITIALIZED
当片段为 LiveData 注册观察者时。
这符合文档推荐的内容:
... getViewLifecycleOwnerLiveData() 然后用与片段视图对应的新初始化的 LifecycleOwner 更新。 onViewCreated() 生命周期回调也在此时被调用
这是设置视图初始状态的合适位置,开始观察其回调更新片段视图的 LiveData 实例 ...
但是,当 Activity 从其
onCreate
回调控制导航时,片段视图的生命周期永远不会到达 STARTED
状态,也不会在显示片段 B 时下降到 DESTROYED
。因此,从片段 B 回到片段 A,前一个生命周期所有者观察 LiveData,片段的新生命周期所有者也试图观察它,抛出异常。
[编辑] 我一直试图找出可能触发此异常的更改。我使用
2.5.1
和 2.6.0
的 androidx.lifecycle:lifecycle-viewmodel-ktx
版本运行代码,我发现异常是由版本 2.6.0
(和 2.6.1
)触发的,但版本 2.5.1
没有触发。我还在片段回调中添加了日志,发现两个版本都调用了onViewCreated
:
onCreateView: Fragment view is INITIALIZED
onViewCreated: Fragment view is INITIALIZED
onDestroyView: Fragment view is INITIALIZED
[/编辑]
我还记录了用户“手动”导航到另一个片段并且片段视图的生命周期从
INITIALIZED
到CREATED
向下到DESTROYED
。
出现了几个问题:生命周期可以从
INITIALIZED
直接进入DESTROYED
状态吗?或者它没有向下走是我这边的错误?
如果不是,正确的做法是什么?一旦生命周期达到
CREATED
状态,是否应该稍后观察LiveData?
或者活动不应该这么早导航到另一个片段?它是否应该在导航离开之前等待片段显示视图(显然这对用户体验来说并不理想)?
我不认为这是一个明确的答案,但我看到了两种解决问题的方法。
一个是降级jetpack生命周期库,特别是
androidx.lifecycle:lifecycle-viewmodel-ktx
,到2.5.1
。
另一个是从activity的
onStart
回调导航到其他Fragments。