引言:“我完全按照教程做了,但应用还是崩溃了!”
\n如果你刚刚开始使用Kotlin进行Android开发,那么你很可能已经遇到过这种情况。你按照教程编写代码,运行应用……然后崩溃。在日志中,你会看到熟悉的java.lang.NullPointerException(或其Kotlin版本)。错误信息是:“Cannot read field… because the view is null”。
这是一个真实存在的痛点,在Stack Overflow上获得了数千次浏览。今天,我们将一劳永逸地解决它。为什么在Kotlin Fragment中访问View时会出现NullPointerException?最重要的是,如何修复它,让你能安心入睡。
\n我们开始吧!
\n\n错误原因是什么?罪魁祸首——生命周期
\n新手遇到Kotlin Fragment中的NullPointerException的主要原因,是不理解Fragment的生命周期。在Activity中很简单:onCreate——然后继续。但在Fragment中,情况要复杂得多。
Fragment会经历几个阶段:
\n- \n
onCreateView()——在这里,布局被填充(创建)。 \n onViewCreated()——在这里,View已经确定存在。 \n onDestroyView()——View被销毁(例如,在屏幕旋转时)。 \n
最常见的错误是在Fragment创建其布局之前尝试访问View。例如,在Fragment的构造函数或onCreate()中。此时,view等于null——因此导致崩溃。
第二个原因是引用泄漏。你将View的引用保存到一个变量中,然后Fragment被重新创建,旧的引用指向了空值。
\n\n典型示例:问题在代码中如何呈现
\n假设你有一个简单的Fragment,其中包含一个文本字段:
\nclass 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尚未创建!
3种修复错误的可行方法
\n让我们看看三种保证能解决Fragment中View访问问题的方法。
\n\n方法1:将代码移到onViewCreated()中
\n最简单且正确的方法是等待View确实存在的时刻。onViewCreated()方法在布局创建后立即被调用。
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()中将引用置空(否则可能导致内存泄漏)。