【问题标题】:How to save and restore lambdas in Android?如何在 Android 中保存和恢复 lambda?
【发布时间】:2019-05-25 03:49:24
【问题描述】:

在 Android 中实现状态恢复时,如何保存和恢复 lambda?

我尝试将其保存为 Serializable 和 Parcelable,但它会引发编译错误。

有什么方法可以保存和恢复它们,还是我应该寻求其他方法?

【问题讨论】:

    标签: android kotlin state-restoration


    【解决方案1】:

    Kotlin lambdas 实现了Serializable,所以它们不能像这样保存:

    override fun onSaveInstanceState(outState: Bundle) {
        outState.putSerializable("YOUR_TAG", myLambda as Serializable)
        super.onSaveInstanceState(outState)
    }
    

    同样,要恢复它们:

    override fun onCreate(savedInstanceState: Bundle?) {
        myLambda = savedInstanceState?.getSerializable("YOUR_TAG") as (MyObject) -> Void
        super.onCreate(savedInstanceState)
    }
    

    (这显然可以在为您提供savedInstanceState 的任何生命周期事件中完成,因为这只是一个示例)

    一些注意事项:

    • 保存它们时,它们需要被强制转换,否则编译器会报错(出于某种原因)。
    • import java.io.Serializable 为必填项。
    • 将其转换回 lambda 类型的方法将引发警告 Unchecked cast: Serializable? to YourLambdaType。这个演员是安全的(假设你正确地推断出可空性!),所以你可以通过使用@Suppress("UNCHECKED_CAST") 安全地抑制这个警告
    • MyObject 必须是 SerializableParcelable,否则会在运行时崩溃。

    现在有一个细节在任何地方都没有被告知,并且在运行时崩溃并且没有有用的崩溃日志。 lambda 的内部实现(即分配它时 { } 内部的内容)不能引用稍后将被释放的对象。 一个经典的例子是:

    // In your MyActivity.kt…
    myLambda = { handleLambdaCallback() } 
    …
    
    private fun handleLambdaCallback() {
        …
    }
    

    这将在运行时崩溃,因为handleLambdaCallback 正在隐式访问this,这将触发尝试递归序列化它可访问的整个对象图,这会在序列化期间的某个时间点失败。

    解决这个问题的一个方法是在 lambda 中发送一个引用。示例:

    // In your MyActivity.kt…
    myLambda = { fragment -> (fragment.activity as MyActivity).handleLambdaCallback() }
    …
    
    private fun handleLambdaCallback() {
        …
    }
    

    这样,我们在调用 lambda 时计算引用,而不是在分配时计算引用。绝对不是最干净的解决方案,但它是我能想到的最好的解决方案,而且它有效。

    随时提出改进和替代解决方案的建议!

    【讨论】:

    • 如果您尝试序列化捕获this 的 lambda,它将尝试递归序列化可从它访问的整个对象图。我希望它会立即失败,而不是在反序列化时。 GCd 所指的对象不是问题,因为分块的重点是重建死对象。
    • 必须将 lambda 转换为 Serializable 的原因应该是这首先向 Kotlin 发出信号以创建可序列化的 lambda。默认情况下,Lambda 是不可序列化的。
    • 你是对的,它在序列化时失败,而不是在反序列化时。序列化整个对象图的有趣点
    • 对我来说,当活动进入 onStop 生命周期事件时它失败了。我还能够使用参数 bundle 在片段中成功传递、检索和 typeCast lambda
    【解决方案2】:

    我应该寻求其他方法吗?

    是的,没有一个很好的理由这样做,你的代码不容易测试,你可能会引入内存泄漏。

    不保存函数,而是保存需要保存的参数(即作用域中的变量),然后像往常一样调用函数。

    示例

    而不是做

    val name = "John Smith"
    val sayHello = { "Hi there, $name" }
    
    startActivity(Intent().apply { putExtra("GREETER", sayHello as Serializable) })
    

    创建一个可以在其他地方使用的函数

    fun sayHello(name: String) = { "Hi there, $name" }
    

    稍后使用恢复的name 参数调用

    【讨论】:

    • 我不明白 lambda 方法是如何天生无法测试并且容易受到内存泄漏的影响。
    • 我认为我的回答改善了内存泄漏,特别是因为现在必须不引用this。但是,我认为您的方法使孩子(例如片段)知道其父母(即片段必须调用(activity as MyActivity).sayHello(name)),这显然不好。要么这样,要么我们会导致“委托”(监听器)方法。我错过了什么吗?
    • @MarkoTopolnik 根据定义,lambdas 没有任何问题,但是从您开始在作用域中使用变量而不是参数的那一刻起,事情就变得一团糟:
    • @MarkoTopolnik 1. 你不能看 lambda 的定义就知道它应该做什么,
    • @MarkoTopolnik 2. 出于同样的原因,很难测试,很难知道要模拟的依赖项(参数)是什么,您必须将它们定义为在范围内可用的变量(而不是只是调用函数)
    猜你喜欢
    • 2011-11-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多