问题
When implementing state restoration in Android, how can I save and restore a lambda?
I tried saving it as Serializable and Parcelable, but it throws a compile error.
Is there any way to save and restore them, or should I seek other approaches?
回答1:
Kotlin lambdas implement Serializable
, so they can't be saved like:
override fun onSaveInstanceState(outState: Bundle) {
outState.putSerializable("YOUR_TAG", myLambda as Serializable)
super.onSaveInstanceState(outState)
}
Similarly, to restore them:
override fun onCreate(savedInstanceState: Bundle?) {
myLambda = savedInstanceState?.getSerializable("YOUR_TAG") as (MyObject) -> Void
super.onCreate(savedInstanceState)
}
(This can obviously be done in any of the lifecycle events that offer you the savedInstanceState
, as this was just an example)
Some notes:
- When saving them, they need to be casted, otherwise compiler complains (for some reason).
import java.io.Serializable
is required.- The method where you're casting it back to your lambda type will throw a warning
Unchecked cast: Serializable? to YourLambdaType
. This cast is safe (assuming you infer the nullability correctly!), so you can safely supress this warning by using@Suppress("UNCHECKED_CAST")
MyObject
must beSerializable
orParcelable
, otherwise it crashes in runtime.
Now there's a detail that is not told anywhere and crashes in runtime with no helpful crash logs. The inner implementation of your lambda (i.e. what's inside the { }
when you assign it) must not have references to objects that will be deallocated in a later moment.
A classic example would be:
// In your MyActivity.kt…
myLambda = { handleLambdaCallback() }
…
private fun handleLambdaCallback() {
…
}
This will crash in runtime because handleLambdaCallback
is implicitly accessing this
, which would trigger an attempt to recursively serialize the entire object graph reachable by it, which would fail at some point during serialization time.
One solution to this problem is to send a reference in the lambda. Example:
// In your MyActivity.kt…
myLambda = { fragment -> (fragment.activity as MyActivity).handleLambdaCallback() }
…
private fun handleLambdaCallback() {
…
}
This way, we are computing the reference when the lambda is invoked, rather than when it's assigned. Definitely not the cleanest solution, but it's the best I could come with, and it works.
Feel free to suggest improvements and alternative solutions!
回答2:
Should I seek other approaches?
Yes, there is not a really good reason to do it, your code won't be easily testable and you could introduce memory leaks.
Instead of saving the function, save the parameters (i.e. variables in the scope) that are needed to be saved, and invoke the function as you usually do.
Example
Instead of doing
val name = "John Smith"
val sayHello = { "Hi there, $name" }
startActivity(Intent().apply { putExtra("GREETER", sayHello as Serializable) })
Create a function that you can use elsewhere
fun sayHello(name: String) = { "Hi there, $name" }
And invoke with the restored name
parameter later
来源:https://stackoverflow.com/questions/53939560/how-to-save-and-restore-lambdas-in-android