Почему фрагмент не сохраняет состояние при повороте экрана?

У меня были некоторые проблемы с получением некоторых пользовательских подклассов DialogPreference внутри PreferenceFragment, чтобы они оставались видимыми при повороте экрана. Я не испытываю этой проблемы при использовании PreferenceActivity, поэтому я не знаю, является ли это ошибкой Android или проблемой с моим кодом, но я хотел бы, чтобы кто-то подтвердил, имеют ли они такой же опыт.

Чтобы проверить это, сначала создайте экран предпочтений, содержащий хотя бы одну DialogPreference (не имеет значения, какой подкласс). Затем отобразите его в PreferenceActivity. Когда вы запустите свое приложение, нажмите на DialogPreference, чтобы отобразилось его диалоговое окно. Затем поверните экран, чтобы изменить ориентацию. Диалог остается видимым?

Затем попробуйте то же самое, но с PreferenceFragment, чтобы отобразить ваши предпочтения вместо PreferenceActivity. Опять же, диалоговое окно остается видимым при повороте экрана?

До сих пор я обнаружил, что диалоговое окно останется видимым при использовании PreferenceActivity, но не при использовании PreferenceFragment. Глядя на исходный код DialogPreference, кажется, что правильное поведение для диалога остается видимым, потому что isDialogShowing информация о состоянии, которая сохраняется при onSaveInstanceState() вызывается переориентация экрана. Поэтому, я думаю, что ошибка может препятствовать тому, чтобы PreferenceFragment (и все внутри него) восстанавливал эту информацию о состоянии.

Если это ошибка Android, то это имеет далеко идущие последствия, потому что любой, кто использует PreferenceFragment, не может сохранять и восстанавливать информацию о состоянии.

Может кто-нибудь подтвердить, пожалуйста? Если это не ошибка, то что происходит?

2 ответа

Решение

Наконец-то разобрался с решением этой проблемы. Оказывается, это не ошибка, а проблема / недосмотр в документации для разработчиков Android.

Видите ли, я следовал учебнику PreferenceFragment здесь. В этой статье говорится, что вы должны сделать следующее для создания экземпляра PreferenceFragment в Activity:

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 будет создан дважды, что приводит к потере его состояния.

Первое создание произойдет через вызов Activity super.onCreate() (показано выше), который будет вызывать onActivityCreated() метод для вашего PreferenceFragment () и onRestoreInstanceState() метод для каждого предпочтения, которое он содержит. Это успешно восстановит состояние всего.

Но когда-то этот призыв к super.onCreate() возвращается, вы можете увидеть, что onCreate() Затем метод будет продолжать создавать PreferenceFragment во второй раз. Поскольку он снова создается бессмысленно (и на этот раз без информации о состоянии!), Все состояние, которое было только что успешно восстановлено, будет полностью отброшено / потеряно. Это объясняет, почему DialogPreference, который может отображаться во время уничтожения Действия, больше не будет виден после повторного создания Действия.

Так в чем же решение? Ну, просто добавьте небольшую проверку, чтобы определить, был ли 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() имеет двойную роль - он может настроить действие в первый раз, или он может восстановить из более раннего состояния.

Ответ здесь привел меня к реализации этого решения.

У меня действительно была эта проблема сама. Есть ошибка, когда 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, И когда вы восстанавливаете состояние (т.е. в onCreate()игнорируй savedInstanceState и вместо этого использовать WorkaroundSavedState.savedInstanceState, Статический держатель не самое чистое решение, но он работает. Просто убедитесь, что он установлен в нуль в вашем onDestroy(),

В любом случае мой DialogFragment не исчезает при повороте экрана (и это без каких-либо configChanges). Дайте мне знать, если этот код решает вашу проблему, и если нет, я посмотрю, что происходит. Также обратите внимание, что я не проверял это в течение PreferenceFragment но вместо других Fragments из класса совместимости или из ActionBarSherlock,