Kotlin片段中的NullPointerException:如何战胜它并保持理智

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

引言:“我完全按照教程做了,但应用还是崩溃了!”

\n

如果你刚刚开始使用Kotlin进行Android开发,那么你很可能已经遇到过这种情况。你按照教程编写代码,运行应用……然后崩溃。在日志中,你会看到熟悉的java.lang.NullPointerException(或其Kotlin版本)。错误信息是:“Cannot read field… because the view is null”。

\n

这是一个真实存在的痛点,在Stack Overflow上获得了数千次浏览。今天,我们将一劳永逸地解决它。为什么在Kotlin Fragment中访问View时会出现NullPointerException?最重要的是,如何修复它,让你能安心入睡。

\n

我们开始吧!

\n\n

错误原因是什么?罪魁祸首——生命周期

\n

新手遇到Kotlin Fragment中的NullPointerException的主要原因,是不理解Fragment的生命周期。在Activity中很简单:onCreate——然后继续。但在Fragment中,情况要复杂得多。

\n

Fragment会经历几个阶段:

\n
    \n
  • onCreateView()——在这里,布局被填充(创建)。
  • \n
  • onViewCreated()——在这里,View已经确定存在。
  • \n
  • onDestroyView()——View被销毁(例如,在屏幕旋转时)。
  • \n
\n

最常见的错误是在Fragment创建其布局之前尝试访问View。例如,在Fragment的构造函数或onCreate()中。此时,view等于null——因此导致崩溃。

\n

第二个原因是引用泄漏。你将View的引用保存到一个变量中,然后Fragment被重新创建,旧的引用指向了空值。

\n\n

典型示例:问题在代码中如何呈现

\n

假设你有一个简单的Fragment,其中包含一个文本字段:

\n
class MyFragment : Fragment() {\n\n    private lateinit var myTextView: TextView\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        // 错误!这里还没有View\n        myTextView = requireView().findViewById(R.id.my_text_view)\n        myTextView.text = "你好!"\n    }\n\n    override fun onCreateView(\n        inflater: LayoutInflater,\n        container: ViewGroup?,\n        savedInstanceState: Bundle?\n    ): View? {\n        return inflater.inflate(R.layout.fragment_my, container, false)\n    }\n}
\n

这段代码会因NullPointerException而崩溃,因为在onCreate()中调用requireView()返回了null。View尚未创建!

\n\n

3种修复错误的可行方法

\n

让我们看看三种保证能解决Fragment中View访问问题的方法。

\n\n

方法1:将代码移到onViewCreated()中

\n

最简单且正确的方法是等待View确实存在的时刻。onViewCreated()方法在布局创建后立即被调用。

\n
class MyFragment : Fragment() {\n\n    private lateinit var myTextView: TextView\n\n    override fun onCreateView(\n        inflater: LayoutInflater,\n        container: ViewGroup?,\n        savedInstanceState: Bundle?\n    ): View? {\n        return inflater.inflate(R.layout.fragment_my, container, false)\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        // 现在view绝对不是null了!\n        myTextView = view.findViewById(R.id.my_text_view)\n        myTextView.text = "一切正常!"\n    }\n}
\n

优点:简单、清晰、始终有效。
\n缺点:需要记住lateinit var,并且不要忘记在onDestroyView()中将引用置空(否则可能导致内存泄漏)。

\n\n

方法2:使用View Binding(推荐)

\n

博客

书籍推荐