使用 savedInstanceState 保存和恢复 Kotlin lambda

Posted

技术标签:

【中文标题】使用 savedInstanceState 保存和恢复 Kotlin lambda【英文标题】:Saving and restoring Kotlin lambda with savedInstanceState 【发布时间】:2018-09-27 12:47:50 【问题描述】:

如何在屏幕旋转之前将 Kotlin lambda 保存在片段中?它可以在 Activity 中工作,但不能在片段中正确工作。执行 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 扩展:

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 ResultInfowho=@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) 

【问题讨论】:

请发布 logcat 异常。 @JeelVankhede 好的,我发布了) 你能告诉我showToast()SamplePermissionFragment 中的代码吗? @JeelVankhede 好的) 【参考方案1】:

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

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

要修复它,在您的情况下,您可以在 lambda 参数之一中引用被调用者,即

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

【讨论】:

【参考方案2】:

您可能对 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))

【讨论】:

是的,您的问题是NullPointerException,因为它试图在context 上显示Toast,而null 已经是null 为什么它可能为空?) 正如你所说,你的Activity/Fragment 得到了轮换,即使在轮换之后你也试图保持它的引用(意思是在ConfigurationChange之后, Activity/Fragment 被重新创建). 我明白了)但是为什么这段代码在Activity(PermissionsActivity)中有效?它与 PermissionsFragment 没有太大区别 这是因为 Fragment lifecycle 从它所属的位置附加到 Activity,您可能希望在 onActivityCreated() 之后执行任何 UI 操作,更多信息来自 developer.android.com/reference/android/app/Fragment#Lifecycle跨度>

以上是关于使用 savedInstanceState 保存和恢复 Kotlin lambda的主要内容,如果未能解决你的问题,请参考以下文章

(转)状态保存 Bundle savedInstanceState

savedInstanceState 是 null 并且 onSaveInstanceState 没有被调用

savedInstanceState - 可见性更改未恢复?

从后台堆栈恢复片段时的 savedInstanceState

如何在 Kotlin 中保存延迟帖子的状态

Android的onCreate里边savedInstanceState啥时候不为空