【问题标题】:Android View Binding in Thread error NullPointerExceptionAndroid View Binding in Thread 错误 NullPointerException
【发布时间】:2021-12-30 02:29:36
【问题描述】:

我已将代码切换到视图绑定,但现在在线程中更新 UI 时遇到问题。该代码在合成语法中运行良好。我收到错误:

java.lang.NullPointerException
    at HomeFragment.getBind(HomeFragment.kt:25)
    at HomeFragment.updateHomeUI$lambda-6(HomeFragment.kt:190)
    at HomeFragment.$r8$lambda$7K03ZbIZrY_5ngvcMBPsw15TPbw(Unknown Source:0)
    at HomeFragment$$ExternalSyntheticLambda10.run(Unknown Source:2)
    at java.lang.Thread.run(Thread.java:919)

我的代码:

class HomeFragment : Fragment(R.layout.fragment_home) {
    private var _binding: FragmentHomeBinding? = null
    private val bind get() = _binding!!  // <-- line 25

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        _binding = FragmentHomeBinding.inflate(inflater, container, false)
        return bind.root
    }

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

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        ...
        updateHomeUI()
    }

    private fun updateHomeUI() {
        Thread {
            while (bind.tvName != null) {  // Stop the loop after changing the fragment
                ...
                // Lots of UI update like this:
                if (activity != null) (activity as MainActivity).runOnUiThread { bind.tvName?.text = str }
                ...
                Thread.sleep(1000)
            }
        }.start()
    }

当我切换到不同的片段时出现错误。如果我是对的,线程在 onDestroyView () 之后运行并且绑定为空。我想出了暂停 onDestroyView 直到 Thread 完成的想法,但我认为这不是最好的解决方案,因为它可能会停止整个应用程序。

override fun onDestroyView() {
    super.onDestroyView()
    threadStop = true
    while (threadRunning) {
        Thread.sleep(1)
    }
    _binding = null
}
private fun updateHomeUI() {
    Thread {
        threadRunning = true
        threadStop = false
        while (!threadStop) {
            ...
        }
        threadRunning = false
    }
}

如何正确避免这个问题?

最好的问候!

【问题讨论】:

  • 如果你只是在线程中使用可空类型 (_binding) 来代替适当的空安全性(比如_binding?.tvName?.text = str)怎么办?
  • 也可能想要while(_binding != null) 在那里...
  • while(binding != null) 不起作用,因为错误来自循环内部。我必须在每次 UI 元素更新时检查绑定,这将是一个丑陋的解决方案,因为我在 UI 中有很多元素
  • 为什么在 onDestroyView 中需要 _binding = null?为什么不删除它?
  • @Anna 在onDestroyView 之后持有对视图的引用可能会导致内存泄漏。看看here

标签: android kotlin android-viewbinding android-threading


【解决方案1】:

如果您放弃 !! getter 并根据需要仅使用可空类型和本地非空变量,您可能会发现这更容易。如果您有很多地方可以在循环内使用绑定,则可以在循环开始时获取它的非空值。

private fun updateHomeUI() {
    Thread {
        // this could also be while(true) since the calls just
        // inside will break out when it goes null
        while (_binding != null) {  // Stop the loop after onDestroyView sets this to null
            val binding = _binding ?: break
            val a = activity as? MainActivity ?: break
            
            
            // Lots of UI update like this:
            a.runOnUiThread { binding.tvName.text = str }
            
            ...
            Thread.sleep(1000)
        }
    }.start()
}

我不确定您现有的bind.tvName != null 循环条件如何在没有首先通过!! 检查的情况下返回 false。默认情况下,绑定类中的视图是非空的(除非它们不存在于所有布局排列中),所以当_binding 为空时,这要么是真的,要么会在bind getter 中失败。

【讨论】:

  • 这似乎可行,但仍然......如果 _binding 在 ?: break 之后变为空,它不会崩溃吗?
  • 否,因为此时您已经保存了对本地非空绑定的引用。即使类成员设置为空,本地成员在该循环的循环中仍将保持非空,并且该对象将保持有效(尽管用户不再可见),因为您仍然拥有对它的引用。你只需要确保在那之后不要在任何地方使用_binding,只使用本地的binding变量。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-20
  • 1970-01-01
  • 2023-01-03
相关资源
最近更新 更多