屏幕旋转时,为什么片段不会保留状态?

时间:2013-01-06 16:32:14

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

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

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

然后尝试相同,但使用PreferenceFragment显示您的首选项而不是PreferenceActivity。同样,旋转屏幕时对话框是否仍然可见?

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

如果是Android错误,则会产生深远的影响,因为使用PreferenceFragment的任何人都无法保存和恢复状态信息。

有人可以确认一下吗?如果它不是一个错误,那么发生了什么?

2 个答案:

答案 0 :(得分:51)

最后找到了解决这个问题的方法。事实证明,这不是一个错误,而是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();
    }
} 

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

第一次创建将通过活动调用super.onCreate()(如上所示)进行,该调用将为您的PreferenceFragment()和{{1}调用onActivityCreated()方法它包含的每个首选项的方法。这些将成功恢复一切状态。

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

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

onCreate()

或者另一种方法是简单地检查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(); } } } 具有双重角色 - 它可以首次设置一个活动,或者它可以从之前的状态恢复。

答案here促使我实现了这个解决方案。

答案 1 :(得分:0)

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

使用多个来源我最终得到了一个解决方案。让您的对话框扩展此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。静态持有者不是最干净的解决方案,但它确实有效。只需确保在onDestroy()中将其设置为空。

在任何情况下,当我旋转屏幕时,我的DialogFragment不会消失(而且没有任何configChanges)。如果此代码解决了您的问题,请告诉我,如果没有,我会看看发生了什么。另请注意,我尚未在PreferenceFragment中对此进行测试,而是在兼容性类或Fragment中测试了其他ActionBarSherlock