Почему фрагмент не сохраняет состояние при повороте экрана?
У меня были некоторые проблемы с получением некоторых пользовательских подклассов 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
но вместо других Fragment
s из класса совместимости или из ActionBarSherlock
,