76

我有一个承载 DialogFragment 的 FragmentActivity。

DialogFragment 执行网络请求并处理 Facebook 身份验证,因此我需要在轮换期间保留它。

我已经阅读了与此问题相关的所有其他问题,但没有一个真正解决了这个问题。

我正在使用 putFragment 和 getFragment 来保存 Fragment 实例并在重新创建活动期间再次获取它。

但是,我总是在 onRestoreInstanceState 中调用 getFragment 时遇到空指针异常。我还想防止对话框在旋转过程中被关闭,但到目前为止我什至无法保留它的实例。

任何想法出了什么问题?

这是我的代码目前的样子:

public class OKLoginActivity extends FragmentActivity implements OKLoginDialogListener
{

    private OKLoginFragment loginDialog;
    private static final String TAG_LOGINFRAGMENT = "OKLoginFragment";


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        FragmentManager fm = getSupportFragmentManager();

        if(savedInstanceState == null)
        {
            loginDialog = new OKLoginFragment(); 
            loginDialog.show(fm, TAG_LOGINFRAGMENT);
        }
    }


    @Override
    public void onSaveInstanceState(Bundle outState)
    {
        getSupportFragmentManager().putFragment(outState,TAG_LOGINFRAGMENT, loginDialog);
    }

    @Override
    public void onRestoreInstanceState(Bundle inState)
    {
        FragmentManager fm = getSupportFragmentManager();
        loginDialog = (OKLoginFragment) fm.getFragment(inState, TAG_LOGINFRAGMENT);
    }

}

这是异常堆栈跟踪:

02-01 16:31:13.684: E/AndroidRuntime(9739): FATAL EXCEPTION: main
02-01 16:31:13.684: E/AndroidRuntime(9739): java.lang.RuntimeException: Unable to start activity ComponentInfo{io.openkit.example.sampleokapp/io.openkit.OKLoginActivity}: java.lang.NullPointerException
02-01 16:31:13.684: E/AndroidRuntime(9739):     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2180)
02-01 16:31:13.684: E/AndroidRuntime(9739):     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2230)
02-01 16:31:13.684: E/AndroidRuntime(9739):     at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:3692)
02-01 16:31:13.684: E/AndroidRuntime(9739):     at android.app.ActivityThread.access$700(ActivityThread.java:141)
02-01 16:31:13.684: E/AndroidRuntime(9739):     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1240)
02-01 16:31:13.684: E/AndroidRuntime(9739):     at android.os.Handler.dispatchMessage(Handler.java:99)
02-01 16:31:13.684: E/AndroidRuntime(9739):     at android.os.Looper.loop(Looper.java:137)
02-01 16:31:13.684: E/AndroidRuntime(9739):     at android.app.ActivityThread.main(ActivityThread.java:5039)
02-01 16:31:13.684: E/AndroidRuntime(9739):     at java.lang.reflect.Method.invokeNative(Native Method)
02-01 16:31:13.684: E/AndroidRuntime(9739):     at java.lang.reflect.Method.invoke(Method.java:511)
02-01 16:31:13.684: E/AndroidRuntime(9739):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
02-01 16:31:13.684: E/AndroidRuntime(9739):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
02-01 16:31:13.684: E/AndroidRuntime(9739):     at dalvik.system.NativeStart.main(Native Method)
02-01 16:31:13.684: E/AndroidRuntime(9739): Caused by: java.lang.NullPointerException
02-01 16:31:13.684: E/AndroidRuntime(9739):     at android.support.v4.app.FragmentManagerImpl.getFragment(FragmentManager.java:528)
02-01 16:31:13.684: E/AndroidRuntime(9739):     at io.openkit.OKLoginActivity.onRestoreInstanceState(OKLoginActivity.java:62)
02-01 16:31:13.684: E/AndroidRuntime(9739):     at android.app.Activity.performRestoreInstanceState(Activity.java:910)
02-01 16:31:13.684: E/AndroidRuntime(9739):     at android.app.Instrumentation.callActivityOnRestoreInstanceState(Instrumentation.java:1131)
02-01 16:31:13.684: E/AndroidRuntime(9739):     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2158)
4

5 回答 5

149

在您的内部,使用值DialogFragment调用。您不需要手动保存片段,框架已经处理了所有这些。调用这将防止您的片段在旋转时被破坏,并且您的网络请求将不受影响。Fragment.setRetainInstance(boolean)true

由于兼容性库的错误,您可能必须添加此代码以阻止您的对话框在旋转时被关闭:

@Override
public void onDestroyView() {
    Dialog dialog = getDialog();
    // handles https://code.google.com/p/android/issues/detail?id=17423
    if (dialog != null && getRetainInstance()) {
        dialog.setDismissMessage(null);
    }
    super.onDestroyView();
}
于 2013-03-16T01:15:25.577 回答
16

dialogFragment与仅使用相比,使用的优点之一alertDialogBuilder正是因为对话片段可以在旋转时自动重新创建自己,而无需用户干预。

但是,当 dialogfragment 没有重新创建自身时,您可能会覆盖onSaveInstanceState但没有调用super

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState); // <-- must call this if you want to retain dialogFragment upon rotation
    ...
}
于 2013-09-23T08:13:04.120 回答
11

这是使用安东尼回答中的修复的便捷方法:

public class RetainableDialogFragment extends DialogFragment {

    public RetainableDialogFragment() {
        setRetainInstance(true);
    }

    @Override
    public void onDestroyView() {
        Dialog dialog = getDialog();
        // handles https://code.google.com/p/android/issues/detail?id=17423
        if (dialog != null && getRetainInstance()) {
            dialog.setDismissMessage(null);
        }
        super.onDestroyView();
    }
}

只要让你DialogFragment扩展这个类,一切都会好起来的。如果您DialogFragments的项目中有多个都需要此修复,这将变得特别方便。

于 2017-05-28T16:47:06.257 回答
0

如果没有任何帮助,并且您需要一个可行的解决方案,您可以安全地进行,每次打开对话框时都将其基本信息保存到活动 ViewModel(并在您关闭对话框时将其从该列表中删除)。此基本信息可能是对话框类型和一些 id(打开此对话框所需的信息)。此 ViewModel 在 Activity 生命周期更改期间不会被销毁。假设用户打开一个对话框以留下对餐厅的引用。所以对话类型是 LeaveReferenceDialog,id 是餐厅 id。打开此对话框时,将此信息保存在可以调用 DialogInfo 的 Object 中,并将此对象添加到 Activity 的 ViewModel。此信息将允许您在调用活动 onResume() 时重新打开对话框:

// On resume in Activity
    override fun onResume() {
            super.onResume()
    
            // Restore dialogs that were open before activity went to background
            restoreDialogs()
        }

哪个电话:

    fun restoreDialogs() {
    mainActivityViewModel.setIsRestoringDialogs(true) // lock list in view model

    for (dialogInfo in mainActivityViewModel.openDialogs)
        openDialog(dialogInfo)

    mainActivityViewModel.setIsRestoringDialogs(false) // open lock
}

当 ViewModel 中的 IsRestoringDialogs 设置为 true 时,对话框信息将不会添加到视图模型中的列表中,这很重要,因为我们现在正在恢复该列表中已经存在的对话框。否则,在使用时更改列表会导致异常。所以:

// Create new dialog
        override fun openLeaveReferenceDialog(restaurantId: String) {
            var dialog = LeaveReferenceDialog()
            // Add id to dialog in bundle
            val bundle = Bundle()
            bundle.putString(Constants.RESTAURANT_ID, restaurantId)
            dialog.arguments = bundle
            dialog.show(supportFragmentManager, "")
        
            // Add dialog info to list of open dialogs
            addOpenDialogInfo(DialogInfo(LEAVE_REFERENCE_DIALOG, restaurantId))
    }

然后在关闭它时删除对话框信息:

// Dismiss dialog
override fun dismissLeaveReferenceDialog(Dialog dialog, id: String) {
   if (dialog?.isAdded()){
      dialog.dismiss()
      mainActivityViewModel.removeOpenDialog(LEAVE_REFERENCE_DIALOG, id)
   }
}

在 Activity 的 ViewModel 中:

fun addOpenDialogInfo(dialogInfo: DialogInfo){
    if (!isRestoringDialogs){
       val dialogWasInList = removeOpenDialog(dialogInfo.type, dialogInfo.id)
       openDialogs.add(dialogInfo)
     }
}


fun removeOpenDialog(type: Int, id: String) {
    if (!isRestoringDialogs)
       for (dialogInfo in openDialogs) 
         if (dialogInfo.type == type && dialogInfo.id == id) 
            openDialogs.remove(dialogInfo)
}

您实际上以相同的顺序重新打开了之前打开的所有对话框。但是他们如何保留他们的信息呢?每个对话框都有自己的 ViewModel,它在 Activity 生命周期中也不会被销毁。因此,当您打开对话框时,您将获得 ViewModel 并像往常一样使用对话框的此 ViewModel 初始化 UI。

于 2020-12-08T10:32:20.687 回答
0

这里的大多数答案都是不正确的,因为它们使用 setRetainInstance(true),但现在从API 28开始不推荐使用。这是我正在使用的解决方案:

fun isDialogVisible(fm: FragmentManager): Boolean {
    val dialog = fm.findFragmentByTag("<FRAGMENT_TAG>")
    return dialog?.isResumed ?: false
}

如果函数返回 false,则只需调用 dialog.show(fm, "<FRAGMENT_TAG>") 再次显示它。

于 2021-08-18T18:22:15.123 回答