为啥不在 Android Fragment 数据绑定中使用 lateinit 修饰符?

Posted

技术标签:

【中文标题】为啥不在 Android Fragment 数据绑定中使用 lateinit 修饰符?【英文标题】:Why not use lateinit modifier in Andrioid Fragment view-binding?为什么不在 Android Fragment 数据绑定中使用 lateinit 修饰符? 【发布时间】:2022-01-01 01:20:08 【问题描述】:

android documentation 中我们有没有lateinit 的数据绑定示例:

private var _binding: ResultProfileBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? 
    _binding = ResultProfileBinding.inflate(inflater, container, false)
    val view = binding.root
    return view


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

为什么我们不使用lateinit,就像我们在活动中使用它一样:

private lateinit var binding: ResultProfileBinding? = null

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? 
    binding = ResultProfileBinding.inflate(inflater, container, false)
    return binding.root

我怀疑它存在内存泄漏问题。能解释一下吗?

【问题讨论】:

【参考方案1】:

我找到了一个很好的解释here。

解释中的片段:

碎片中的泄漏是如何发生的?首先,我们需要从回顾开始 片段的重要细微差别。它们有两个不同的生命周期:

它自己的生命周期(onCreateonDestroy) 这是视图的生命周期(onCreateViewonDestroyView

单个生命周期有两个生命周期 屏幕可能有问题。它们在不同的地方被创建和销毁 例如,当将片段放在后台堆栈时。 具体来说,在调用onDestroyView 之后保留视图将 泄漏。当片段在后堆栈上时会发生这种情况,尽管 它的视图被破坏了,片段本身没有。垃圾 收集器无法清除对这些视图的引用。

还有一个来自this Stack Overflow 的 sn-p 答案:

您必须取消对 onDestroyView 中视图的引用 这是片段不再使用视图的标志 系统,如果它不适合您,它可以安全地被垃圾收集 继续参考View

【讨论】:

【参考方案2】:

看看这个例子

// here a firestore database uses callback to be executed when the document recieved 
db.collection("cities").document("SF").get()
        .addOnSuccessListener  document ->
            if (document != null) 
                binding.textView.text = document.data.toString()
             else 
                Log.d(TAG, "No such document")
            
        

如果用户在收到文档之前打开片段并关闭它(这意味着片段不再被使用,如果变量为空或不再使用,则垃圾收集器应该清除它的所有变量)

现在让我们讨论一下lateinit 的场景

private lateinit var binding: ResultProfileBinding

车库收集器不会清除binding,因为它仍在回调中使用,并且片段将保留在内存中导致内存泄漏,然后执行回调并设置用户不知道的文本那是因为他留下了碎片

想象一下,如果用户多次执行此场景!

那么可空绑定呢?

private var _binding: ResultProfileBinding? = null
private val binding get() = _binding!!

您在 onDestroyView 中将其设置为 null,以便可以对 binding 和片段进行垃圾收集(无内存泄漏)

但是当回调执行时会发生什么?

你会得到一个NullPointerException,所以要注意这一点

无论用户打开和关闭片段多少次,它都会被垃圾回收

尝试使用此代码,您可以使用Android Studio Profiler 来查看设备内存和/或leakCanary 来获取有关应用程序内存泄漏的通知

【讨论】:

【参考方案3】:

片段中的binding 可能会导致内存泄漏,如果它们在onDestroyView 中未设置为空。这就是为什么我们在onDestroyView 中将_binding 设置为null

在使用lateinit 时,我们不能将lateinit 属性分配给null。因此,如果我们使用lateinit binding,我们将无法在onDestroyView 中将其设置为空,这将导致memory leak。这就是为什么 android 文档建议使用 nullable 变量的原因。

对于activities,我们不需要将binding 分配给null,因为它们不会导致内存泄漏,我们可以为binding 使用lateinit 属性。

【讨论】:

但是为什么,你能用一个例子来解释(场景)。这个答案可以在internet找到。 您所说的一切与文档所说的相同。您能否提供一种可能发生内存泄漏的情况? @praveen 片段比他们的观点更长寿,如果观点被破坏,那么它可以持有那个绑定引用。 这不是内存泄漏的传统定义。这更像是临时内存泄漏,其中 Fragment 实例挂在对操作系统想要释放的过时视图的引用上,因为 Fragment 在后台堆栈上。

以上是关于为啥不在 Android Fragment 数据绑定中使用 lateinit 修饰符?的主要内容,如果未能解决你的问题,请参考以下文章

Android:何时/为啥我应该使用 FrameLayout 而不是 Fragment?

为啥android fragment 不调用 oncreateview方法

为啥此错误显示“不兼容的类型”。 “必需:android.support.v4.app.Fragment”?

关闭飞行模式后,为啥 Android 应用程序会通过 Activity 和 Fragment 生命周期方法

为啥不在主线程(Android)中完成所有操作? [关闭]

从画廊返回后,不在 Fragment 中调用 onActivityResult