Lateinit 属性未在 Fragment 上初始化

Posted

技术标签:

【中文标题】Lateinit 属性未在 Fragment 上初始化【英文标题】:Lateinit properties were not initialized on Fragment 【发布时间】:2020-01-18 05:08:47 【问题描述】:

我已经编写了我的第一个 Kotlin 和 android 应用程序,我在这个应用程序上遇到了很多崩溃。

所有这些都与lateinit关键字相关。

我得到了像这样的崩溃:

Caused by e.y: lateinit property coordinator has not been initialized

和:

Fatal Exception: java.lang.RuntimeException
Unable to start activity ComponentInfoapp.myapp/mypackage.myapp.Controllers.MainActivity: e.y: lateinit property coordinator has not been initialized

Caused by e.y
lateinit property coordinator has not been initialized
myapp.com.myapp.Controllers.Fragments.Parents.MyParentFragment.getCoordinator


例如,最后一个跟踪与我在片段上设置的变量有关,一旦我像这样初始化它:

        fun newInstance(mainObject: MyObject, anotherObject: AnotherObject, coordinator: FragmentCoordinator): MyFragment 
            val fragment = MyFragment()
            fragment.mainObject = mainObject
            fragment.anotherObject = ticket
            fragment.coordinator = coordinator
            return fragment
        

片段侧看起来像:

class MyFragment: MyParentFragment() 

     companion object 
        fun newInstance(mainObject:...)
     

    lateinit var mainObject: MainObject
    lateinit var anotherObject: AnotherObject
    ... 

我的理解是,当应用程序更改其状态(背景...)时,此 lateinit 属性引用会丢失,一旦代码调用此变量属性为 null 并且应用程序崩溃...请理解,一旦创建了所有片段这个变量不为空,它们被分配。

我找到了这篇文章:https://www.bignerdranch.com/blog/kotlin-when-to-use-lazy-or-lateinit/,它指出了发生的事情,并且可以使用by lifecycleAwareLazy(lifecycle) 将变量链接到应用程序生命周期进行修复,他补充说:这两个属性委托解决了一个问题,但不完全。 它们仍然包含内存泄漏。为什么他说“它们仍然包含内存泄漏?”

那么在 android 上,我如何确保无论应用程序从后台返回还是其他什么情况下始终设置我的变量,因为当应用程序显示片段时会发生这种情况,一些代码运行良好,然后需要调用这个 lateinit变量并崩溃,因为该变量不再存在,而是在创建片段时存在。

感谢您的帮助。

【问题讨论】:

您必须在 kotlin 中初始化您的 lateinit var,否则它会在片段中引发错误,您可以使用 onViewCreated(...) 函数来初始化您的视图,即。 lateinit var imgAddress: AppCompatImageView imgAddress = view.findViewById(R.id.imgSelectAddress) 作为 AppCompatImageView。也不需要声明 var。在活动中,您可以直接将其与 XML id 名称一起使用。 【参考方案1】:

当您的活动被重新创建时,FragmentManager 调用每个附加的 Fragment 的 0 参数构造函数来重新创建它们。

您的newInstance 方法应该只创建一个Bundle 并在Fragment.setArguments(Bundle) 中使用它,因为这是确保这些参数在配置更改后仍然有效的唯一方法。然后在onCreate 中,您可以通过getArguments 检索Bundle

如果您需要无法放入 Bundle 的参数,则必须在 Fragments onCreate 或其他方法中注入/重新创建/获取它们。

【讨论】:

【参考方案2】:

lateinit 表示您没有在声明时分配变量值,但您必须在使用它之前在 java 类的后期分配值。

如果你在赋值前尝试获取lateinit变量的值,那么lateinit property coordinator has not been initialized就会发生异常。

如果你不确定你的 lateinit 变量是否被赋值,你可以使用 为 lateinit 变量提供 isInitialized 方法以避免崩溃。

示例:

if(::varibleName.isInitialized)
// do something

如果您想在配置更改后保存变量值,您必须使用ViewModel

如果您不使用 ViewModel,则通过 bundle 传递值并再次从中获取值并重新分配变量,但请注意,如果您使用 bundle 传递的值过多,则会出现 TooLargeTransition 异常。您还可以使用onSaveInstanceState 来存储更新的值并在onCreate 方法中检索它。

【讨论】:

我认为没有必要提及 ViewModel,它会像现在一样崩溃。您的版本 1 答案更好。 如果varibleName 在伴随对象中怎么办?【参考方案3】:
    fun newInstance(mainObject: MyObject, anotherObject: AnotherObject, coordinator: FragmentCoordinator): MyFragment 
        val fragment = MyFragment()
        fragment.mainObject = mainObject
        fragment.anotherObject = ticket
        fragment.coordinator = coordinator
        return fragment
    

你不能像这样创建Fragment,你不能像这样设置字段变量。

系统在内存不足的情况下使用no-arg 构造函数通过反射重新创建片段。你的代码,在这里,newInstance 和东西 - 永远不会运行。从不。

另一方面,setArguments(Bundle 参数由系统保留,您可以使用 getArguments() 获取它们。

因此,目前,如果您的应用程序被置于后台,被 Android 杀死,然后您再次从启动器打开应用程序,那么您将崩溃。

查看相关问题:

Singleton object becomes null after app is resumed(我在这里描述了如何在开发环境中轻松重现此内容)

也可能是App crash after activity has been killed in background

【讨论】:

以上是关于Lateinit 属性未在 Fragment 上初始化的主要内容,如果未能解决你的问题,请参考以下文章

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

Lateinit 变量未在 TestNG 的 @BeforeSuite 中初始化

Kotlin类的初始化 ④ ( lateinit 延迟初始化 | ::属性名称.isInitialized 检查属性是否初始化 | lazy 惰性初始化 )

Kotlin类的初始化 ④ ( lateinit 延迟初始化 | ::属性名称.isInitialized 检查属性是否初始化 | lazy 惰性初始化 )

使用“by lazy”与“lateinit”进行属性初始化

Dagger2 + Kotlin:lateinit 属性尚未初始化