【问题标题】:Viewbinding in Fragment causes KotlinNullPointerException running within lifecycle scoped coroutineFragment 中的视图绑定导致 KotlinNullPointerException 在生命周期范围的协程内运行
【发布时间】:2020-10-12 08:04:57
【问题描述】:

我正在像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

为每个 Lifecycle 对象定义一个 LifecycleScope。当生命周期被销毁时,在这个范围内启动的任何协程都会被取消。

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

【问题讨论】:

  • myViewModel.loadFromNetwork() 是什么样的挂起函数?是可取消的挂起功能吗?

标签: android kotlin kotlin-coroutines android-viewbinding


【解决方案1】:

协程取消是cooperative。这意味着检查取消是协程本身的责任。协程库中的大多数(或可能是全部)暂停操作会检查取消,但如果您不调用其中任何一个,则需要按照here 的描述使您的代码可取消。

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

另外请注意,取消只是普通的CancellationException,所以请注意不要意外收到它。

【讨论】:

    【解决方案2】:

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

    lateinit var job:Job
    

    然后像这样分配它

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

    并在将 null 分配给 _binding 之前在 onDestroView 方法中取消它。

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

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

    【讨论】:

    • 不是完全相同的问题,但您的评论帮助我解决了我的问题。谢谢
    【解决方案3】:

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

    【讨论】:

    • 当我再次阅读文档时,我也有同样的怀疑。我认为他们都指向同一件事,但只是为了确保我已经使用viewLifecycleOwner.lifecycleScope.launch {} 验证了崩溃发生。
    • 我也面临同样的问题。有什么解决办法吗?
    • 您是否尝试从 onDestroyView 中删除 _binding = null?
    • 这样可以解决问题,但会造成内存泄漏?
    猜你喜欢
    • 2021-12-21
    • 2019-12-11
    • 1970-01-01
    • 1970-01-01
    • 2021-12-20
    • 1970-01-01
    • 1970-01-01
    • 2020-06-23
    • 1970-01-01
    相关资源
    最近更新 更多