Fragment 中的视图绑定导致生命周期范围协程中的 KotlinNullPointerException 运行

Viewbinding in Fragment causes KotlinNullPointerException running within lifecycle scoped coroutine

我正在像 suggested in Google docs 这样设置我的片段:

    private var _binding: MyBinding? = null
    private val binding get() = _binding!!
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        _binding = MyBinding.inflate(inflater, container, false)
    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

现在我正在调用一个协程,据我所知,它的范围应该在这个片段的生命周期内。它有更长的网络调用,然后成功:

        lifecycleScope.launch(Dispatchers.Main) {
            when (myViewModel.loadFromNetwork(url)) {
                true -> responseSuccess()
                false -> responseFailure()
            }
        }
    private suspend fun responseSuccess() {
        binding.stateSuccess.visibility = View.VISIBLE
        // ...
    }

现在,当我在 loadFromNetwork 仍在加载时按下 Android 系统后退按钮时,片段被销毁并调用 onDestroyView()。因此 binding 现在是 null。我收到 kotlin.KotlinNullPointerException。我不太明白的是为什么 responseSuccess() 仍然被执行,即使我认为 lifecycleScope 是专门针对这种情况的。根据 Google Docs:

A LifecycleScope is defined for each Lifecycle object. Any coroutine launched in this scope is canceled when the Lifecycle is destroyed.

我知道可以通过一些更改和一些手动空值检查来修复此代码,但我想了解如何在没有样板的情况下以预期的方式修复此问题。如果不完全是这样,使用 lifecycleScope 来了解生命周期的目的是什么?

您正在使用与片段视图的生命周期不同的生命周期范围。所以你必须使用不同的范围 viewLifecycleOwner.lifecycleScope.launch {}。顺便说一句,link 到 Google 文档正是这么说的:)

协程取消是 cooperative. It means it's a responsibility of a coroutine itself to check for cancellation. Most (or may be all) suspend operations in the coroutines library check for cancellation, but if you don't call any of them, you need to make your code cancellable as described here

在协程中使用视图的更好选择是使用 lifecycle extensions,当生命周期状态不在所需状态时,它会自动挂起/取消协程。

另请注意,取消只是一个常规 CancellationException,因此请注意不要误触。

好吧,这可能不是一种非常干净的处理方式,但我建议您自己取消 onDestroyView 中的作业
定义 class 级别的工作,例如

lateinit var job:Job

然后像这样赋值

job = lifecycleScope.launch(Dispatchers.Main) {
            when (myViewModel.loadFromNetwork(url)) {
                true -> responseSuccess()
                false -> responseFailure()
            }

并在 onDestroView 方法中取消它,然后再将 null 分配给 _binding。

override fun onDestroyView() {
        super.onDestroyView()
        job.cancel()
        _binding = null
    }

你得到 NULL POINTER EXCEPTION 的原因是片段有两个生命周期。 1)生命周期和视图生命周期。 _binding 在 onDestroyView 中被分配给 null 但片段生命周期仍然存在,因此协程的工作正在完成它的工作,当网络响应到达它时 运行 启动块并想要访问当时为 null 的绑定对象。