如何通过旋转正确保留DialogFragment?


74

我有一个承载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)

您能给我们异常堆栈跟踪吗?我认为您可能希望专注于问题的这一方面。
Brian Attwell

删除对putFragment和getFragment的调用时会发生什么?如果当前在屏幕上显示DialogFragment,则应在更改配置后恢复Fragment的状态。
user697495

我认为如果您super.onSaveInstanceState(outState)在重写的onSaveInstanceState方法中添加调用,NullPointerException将消失。
burnttoast13年

Answers:


146

在您的内部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();
}

这成功了。我认为我之前做错的是我需要将showDialg()代码包装在onCreateView内,对saveInstanceState进行空检查
ch3rryc0ke 2013年

出现错误-java.lang.RuntimeException:无法销毁活动{com.attchment / com.attchment.MainActivity}:java.lang.IllegalStateException:DialogFragment已经使用了OnDismissListener,无法替换。
abh22ishek 2015年

13
嘿,Google,来吧,这不是火箭科学。为什么不修复它?:)
迭戈

1
@Diego Google并不擅长快速修复错误。CoordinatorView充满了2年未修复的错误。.MapFragment中存在一个3年后修复的错误。至少MapFragment错误最终得到修复:)
A. Steenbergen

Fragment.java这样说setRetainInstance:“这只能与不在后堆栈中的片段一起使用。” DialogFragment肯定在后面的堆栈中,请参阅show。有人知道此代码注释是否完全错误?
androidguy

16

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

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

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState); // <-- must call this if you want to retain dialogFragment upon rotation
    ...
}

1
对此+1,仅是说,根据我的经验,这适用于视图,我们仍然需要保存变量
Badr

11

这是使用antonyt的答案中的修复程序的一种便捷方法:

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的项目中有多个都需要此修复程序,这将变得特别方便。


0

如果没有任何帮助,并且您需要一个可行的解决方案,则可以放心一点,每次打开对话框时,请将其基本信息保存到活动ViewModel中(在关闭对话框时将其从此列表中删除)。此基本信息可以是对话框类型和某些ID(打开此对话框所需的信息)。在Activity生命周期更改期间,不会销毁此ViewModel。假设用户打开一个对话框以留下对餐厅的引用。因此,对话框类型为LeaveReferenceDialog,而id为餐厅ID。打开此对话框时,将此信息保存在一个可以调用DialogInfo的对象中,然后将此对象添加到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)
   }
}

在活动的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,在活动生命周期中也不会销毁它。因此,当您打开对话框时,您将获得ViewModel并一如既往地使用对话框的ViewModel来初始化UI。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.