使用saveInstanceState保存和还原Kotlin lambda

时间:2018-09-27 12:47:51

标签: android lambda kotlin

如何在屏幕旋转之前以片段形式保存Kotlin lambda?它可以在“活动”中运行,但不能纠正片段中的工作。如果执行lambda表达式包含对PermissionsFragment的子类的方法的调用,则会发生异常,为什么?

本身存储lambda的类:

class ActionKeeper(var action: ((isGranted: Boolean) -> Unit)? = null) : Serializable

在屏幕旋转时将lambda保存到ActionKeeper的片段:

abstract class PermissionsFragment : Fragment() {

    private var action: ((isGranted: Boolean) -> Unit)? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        if (savedInstanceState != null) {
            restoreState(savedInstanceState)
        }
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putSerializable("actionKeeper", ActionKeeper(action))
    }

    private fun restoreState(state: Bundle) {
        val keeper = state.getSerializable("actionKeeper") as ActionKeeper
        action = keeper.action
    }

    fun usePermission(permission: String, action: (isGranted: Boolean) -> Unit) {
        if (!isPermissionGranted(permission)) {
            this.action = action
            requestPermissions(arrayOf(permission), 1)
        } else {
            action(true)
        }
    }

   /* ........ */
}

从PermissionsFragment扩展的类:

class SamplePermissionsFragment : PermissionsFragment() {
    private var toast: Toast? = null

    private fun doWithPermission() {
        usePermission(Manifest.permission.SEND_SMS) { isGranted ->
            if (isGranted) {
                showToast("Fragment permission granted")
            } else {
                showToast("Fragment permission refused")
            }
        }
    }

    private fun showToast(text: String) {
        toast?.cancel()
        toast = Toast.makeText(context!!, text, Toast.LENGTH_SHORT).apply { show() }
    }
}

Logcat exteption:

2018-09-27 15:57:17.068 5569-5569/com.alexchurkin.permissionsample E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.alexchurkin.permissionsample, PID: 5569
    java.lang.RuntimeException: Failure delivering result ResultInfo{who=@android:requestPermissions:, request=65537, result=-1, data=Intent { act=android.content.pm.action.REQUEST_PERMISSIONS (has extras) }} to activity {com.alexchurkin.permissionsample/com.alexchurkin.permissionsample.fragment.FragmentHostActivity}: kotlin.KotlinNullPointerException
        at android.app.ActivityThread.deliverResults(ActivityThread.java:4196)
        at android.app.ActivityThread.handleSendResult(ActivityThread.java:4239)
        at android.app.ActivityThread.-wrap20(ActivityThread.java)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1599)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:165)
        at android.app.ActivityThread.main(ActivityThread.java:6365)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:883)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:773)
     Caused by: kotlin.KotlinNullPointerException
        at com.alexchurkin.permissionsample.fragment.SamplePermissionsFragment.showToast(SamplePermissionsFragment.kt:56)
        at com.alexchurkin.permissionsample.fragment.SamplePermissionsFragment.access$showToast(SamplePermissionsFragment.kt:13)
        at com.alexchurkin.permissionsample.fragment.SamplePermissionsFragment$doWithPermission$1.invoke(SamplePermissionsFragment.kt:35)
        at com.alexchurkin.permissionsample.fragment.SamplePermissionsFragment$doWithPermission$1.invoke(SamplePermissionsFragment.kt:13)
        at com.alexchurkin.fastpermissions.fragments.PermissionsFragment.onRequestPermissionsResult(PermissionsFragment.kt:38)
        at android.support.v4.app.FragmentActivity.onRequestPermissionsResult(FragmentActivity.java:860)
        at android.app.Activity.dispatchRequestPermissionsResult(Activity.java:7268)
        at android.app.Activity.dispatchActivityResult(Activity.java:7120)
        at android.app.ActivityThread.deliverResults(ActivityThread.java:4192)
        at android.app.ActivityThread.handleSendResult(ActivityThread.java:4239) 
        at android.app.ActivityThread.-wrap20(ActivityThread.java) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1599) 
        at android.os.Handler.dispatchMessage(Handler.java:102) 
        at android.os.Looper.loop(Looper.java:165) 
        at android.app.ActivityThread.main(ActivityThread.java:6365) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:883) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:773) 

2 个答案:

答案 0 :(得分:0)

您可能对 context

有疑问

尝试下面的代码,让我知道:

private fun showToast(text: String) {
    toast?.cancel()
    activity?.let {
        toast = Toast.makeText(it, text, Toast.LENGTH_SHORT).apply { show() }
    }
}

编辑:

ActionKeeper 的对象另存为:

outState.putSerializable("actionKeeper", object: ActionKeeper(action))

答案 1 :(得分:0)

您的代码中的问题是lambda中有一些引用,这些引用将来会被释放(因此不可访问)。保存状态时,Android会将指针(引用)保存在该lambda中,因此它不能指向无效的内存。

这在这里有更好的解释:How to save and restore lambdas in Android?

要解决此问题,您可以在lambda参数之一(即

)中引用被调用方
private var action: ((Fragment, Boolean) -> Unit)? = null