【问题标题】:Why won't Fragment retain state when screen is rotated?为什么屏幕旋转时片段不保留状态?
【发布时间】:2012-12-20 11:42:16
【问题描述】:

我在让 PreferenceFragment 中的一些自定义 DialogPreference 子类在屏幕旋转时保持可见时遇到了一些麻烦。我在使用 PreferenceActivity 时没有遇到这个问题,所以我不知道这是 Android 错误还是我的代码有问题,但我希望有人确认他们是否有相同的经历。

要对此进行测试,首先创建一个包含至少一个 DialogPreference 的首选项屏幕(哪个子类无关紧要)。然后在 PreferenceActivity 中显示它。当您运行您的应用程序时,按下 DialogPreference 以显示它的对话框。然后旋转屏幕以改变方向。对话框是否仍然可见?

然后尝试相同的方法,但使用 PreferenceFragment 来显示您的偏好,而不是 PreferenceActivity。同样,当您旋转屏幕时对话框是否仍然可见?

到目前为止,我发现如果使用 PreferenceActivity,对话框将保持可见,但如果使用 PreferenceFragment,则不会。查看source code for DialogPreference,似乎正确的行为是让对话框保持可见,因为isDialogShowing 是在屏幕重新定向时调用onSaveInstanceState() 时保存的状态信息。因此,我认为一个错误可能会阻止 PreferenceFragment(以及其中的所有内容)恢复该状态信息。

如果是 Android 的 bug,那么它的影响会很深远,因为任何使用 PreferenceFragment 的人都无法保存和恢复状态信息。

有人可以确认一下吗?如果不是bug,那是怎么回事?

【问题讨论】:

    标签: android android-fragments android-preferences preferenceactivity dialog-preference


    【解决方案1】:

    终于想出了解决这个问题的办法。事实证明这不是错误,而是 Android 开发人员文档中的问题/疏忽。

    你看,我正在关注 PreferenceFragment 教程here。那篇文章告诉您执行以下操作以在 Activity 中实例化您的 PreferenceFragment:

    public class SettingsActivity extends Activity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            // Display the fragment as the main content.
            getFragmentManager().beginTransaction()
                    .replace(android.R.id.content, new SettingsFragment())
                    .commit();
        }
    } 
    

    这样做的问题是,当您更改屏幕方向(或任何其他破坏并重新创建 Activity 的操作)时,您的 PreferenceFragment 将被创建两次,这就是导致它失去它的状态。

    第一次创建将通过 Activity 对 super.onCreate() 的调用(如上所示)进行,它将为您的 PreferenceFragment () 调用 onActivityCreated() 方法,并为每个 Preference 调用 onRestoreInstanceState() 方法它包含了。这些将成功恢复一切的状态。

    但是一旦对super.onCreate() 的调用返回,您可以看到onCreate() 方法将继续创建PreferenceFragment 时间。因为它被毫无意义地再次创建(这一次,没有状态信息!),所有刚刚成功恢复的状态都将被完全丢弃/丢失。这解释了为什么在 Activity 被销毁时可能显示的 DialogPreference 在 Activity 重新创建后将不再可见。

    那么解决方法是什么?好吧,只需添加一个小检查以确定 PreferenceFragment 是否已创建,如下所示:

    public class SettingsActivity extends Activity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            Fragment existingFragment = getFragmentManager().findFragmentById(android.R.id.content);
            if (existingFragment == null || !existingFragment.getClass().equals(SettingsFragment.class))
            {
                // Display the fragment as the main content.
                getFragmentManager().beginTransaction()
                    .replace(android.R.id.content, new SettingsFragment())
                    .commit();
            }
        }
    }
    

    或者另一种方法是简单地检查 onCreate() 是否意味着恢复状态,如下所示:

    public class SettingsActivity extends Activity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            if (savedInstanceState == null)
            {
                // Display the fragment as the main content.
                getFragmentManager().beginTransaction()
                    .replace(android.R.id.content, new SettingsFragment())
                    .commit();
            }
        }
    }
    

    所以我想这里学到的教训是onCreate() 具有双重作用——它可以第一次设置一个 Activity,或者它可以从更早的状态恢复。

    here 的回答让我意识到了这个解决方案。

    【讨论】:

    • 第三个选项是检查“第一个”onCreate执行的标准方式
    • if(savedInstanceState == null){} 该语句只会在第一次创建 Activity 时解析为 true
    • 这不就是我上面说的吗……?
    • +1 - 我关注的演示还在onCreate() 中创建了一个new Fragment()。但这是一个棘手的问题。很好的解释。
    • 我不知道为什么,但是检查片段是否已经存在的第一种方法对我不起作用。只有检查 if (savedInstanceState == null) 才有效。
    【解决方案2】:

    我自己也确实遇到过这个问题。有一个错误,DialogFragment 没有恢复状态,因为它是空的,或者至少它发生在我身上。

    使用多个来源,我最终得到了一个可行的解决方案。让您的对话框扩展此BaseDialogFragment

    import android.app.Dialog;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.support.v4.app.DialogFragment;
    
    import com.actionbarsherlock.app.SherlockDialogFragment;
    
    public class BaseDialogFragment extends DialogFragment {
    
        @Override
        public void onCreate(Bundle savedInstanceState)
        {
            if (savedInstanceState == null || savedInstanceState.isEmpty())
                savedInstanceState = WorkaroundSavedState.savedInstanceState;
    
            setRetainInstance(true);
            Log.d("TAG", "saved instance state oncreate: "
                    + WorkaroundSavedState.savedInstanceState);
            super.onCreate(savedInstanceState);
        }
    
        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState)
        {
            if (savedInstanceState == null || savedInstanceState.isEmpty())
                savedInstanceState = WorkaroundSavedState.savedInstanceState;
            Log.d("TAG", "saved instance state oncretaedialog: "
                    + WorkaroundSavedState.savedInstanceState);
    
            return super.onCreateDialog(savedInstanceState);
        }
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            if (savedInstanceState == null || savedInstanceState.isEmpty())
                savedInstanceState = WorkaroundSavedState.savedInstanceState;
    
            Log.d("TAG", "saved instance state oncretaeview: "
                    + WorkaroundSavedState.savedInstanceState);
    
            return super.onCreateView(inflater, container, savedInstanceState);
        }
    
        @Override
        public void onDestroyView() // necessary for restoring the dialog
        {
            if (getDialog() != null && getRetainInstance())
                getDialog().setOnDismissListener(null);
    
            super.onDestroyView();
        }
    
        @Override
        public void onSaveInstanceState(Bundle outState)
        {
            // ...
    
            super.onSaveInstanceState(outState);
            WorkaroundSavedState.savedInstanceState = outState;
            Log.d("TAG", "saved instance state onsaveins: "
                    + WorkaroundSavedState.savedInstanceState);
    
        }
    
        @Override
        public void onDestroy()
        {
            WorkaroundSavedState.savedInstanceState = null;
            super.onDestroy();
        }
    
        /**
         * Static class that stores the state of the task across orientation
         * changes. There is a bug in the compatibility library, at least as of the
         * 4th revision, that causes the save state to be null in the dialog's
         * onRestoreInstanceState.
         */
        public static final class WorkaroundSavedState {
            public static Bundle savedInstanceState;
        }
    }
    

    请注意,在其方法具有savedInstanceState 参数的任何子类中,您可能必须使用WorkaroundSavedState.savedInstanceState 调用super。当您恢复状态时(即在onCreate() 中,只需忽略savedInstanceState 而是使用WorkaroundSavedState.savedInstanceState。静态保持器不是最干净的解决方案,但它有效。只需确保将其设置为null in你的onDestroy()

    无论如何,当我旋转屏幕时,我的DialogFragment 不会消失(而且没有任何configChanges)。让我知道这段代码是否解决了您的问题,如果没有,我会看看发生了什么。另请注意,我没有在PreferenceFragment 中测试过这个,而是来自兼容性类或ActionBarSherlock 的其他Fragments。

    【讨论】:

    • 为什么要引入第三方依赖(ActionBarSherlock)?
    • 你不需要。只是让它扩展DialogFragment
    • 我刚刚编辑了我的答案。就我个人而言,我只是使用SherlockDialogFragment,因为我使用的是 ABS,但我的答案也应该在没有 ABS 的情况下工作。立即尝试让您自己的 DialogFragments 扩展此类。
    • 想出了一个解决方案。看我的回答。
    • 哦,我明白了;我们可能遇到了不同的问题,因为我没有在 onCreate 中重新显示片段。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-01-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-12
    相关资源
    最近更新 更多