Фрагмент воссоздается каждый раз после изменения ориентации, невозможно восстановить состояние

Обновление: Оказывается, проблема в другом месте. Спасибо @Luksprog за указание на то, что я упустил из виду.

  1. Проект создается с использованием шаблона навигационного ящика Android Studio. Ящик реализован в классе NavigationDrawerFragment.
  2. Фрагмент, содержащий пейджер представления, добавляется при выборе определенного элемента в ящике. Код реализован моей домашней деятельностью.
  3. Когда экран вращается, вызывается метод onCreate() для NavigationDrawerFragment, сохраняющий последний выбранный элемент.
  4. И вот что пошло не так — после воссоздания NavigationDrawerFragment снова вызовет selectItem(), что вызовет обработчик выбранного пункта меню. Это приводит к тому, что ListFragment восстанавливается Android.

Этого можно избежать, проверив активный пункт меню в моем коде обработчика выбора меню.


Я хочу сохранить последний индекс страницы просмотра ViewPager, когда действие воссоздается по какой-либо причине, например. изменение ориентации.

ViewPager находится в Fragment (с именем ListFragment), который прикреплен к действию. Я использую библиотеку совместимости, поэтому фрагмент является подклассом android.support.v4.app.Fragment.

Я подумал, что это можно сделать, переопределив метод onSaveInstanceState() и добавив соответствующую логику в onCreate(), как указано в документ:

Для правильной обработки перезапуска важно, чтобы ваша активность восстанавливала свое предыдущее состояние через обычный жизненный цикл активности, в котором Android вызывает onSaveInstanceState() перед тем, как уничтожить вашу активность, чтобы вы могли сохранить данные о состоянии приложения. Затем вы можете восстановить состояние во время onCreate() или onRestoreInstanceState().

Но ситуация кажется иной для фрагментов. Индекс страницы может быть правильно восстановлен, когда я перехожу от этого ListFragment к другому действию и нажимаю «назад». Однако, когда я поворачиваю свое устройство, индекс страницы теряется.

Я добавил некоторые журналы, чтобы увидеть, что не так. Из журнала я обнаружил, что хотя onSaveInstanceState() из ListFragment (я назову его ListFragment A) вызывается правильно, этот конкретный класс Fragment больше не отображается в действии. Когда ориентация изменилась и действие воссоздано, Android вызывает onSaveInstanceState(), а затем onDetach(), чтобы отсоединить этот фрагмент. Затем Android создает новый экземпляр ListFragment (я назову его ListFragment B) и прикрепляет его к новой чередующейся активности. Этот ListFragment B имеет пустой savedInstanceState, переданный конструктору, и, таким образом, последний индекс страницы (и любая конфигурация в saveInstanceState фрагмента A) потеряны.

На самом деле новый экземпляр ListFragment будет создаваться каждый раз, когда происходит поворот экрана, но, похоже, старые не будут уничтожены. Я вижу журналы, как показано ниже, когда я поворачиваю устройство:

D/ListFragment﹕ [1110257048] onSaveInstanceState() called, storing last page index 3
D/ListFragment﹕ [1109835992] onSaveInstanceState() called, storing last page index 0
D/ListFragment﹕ [1108826176] onSaveInstanceState() called, storing last page index 0
D/ListFragment﹕ [1108083096] onSaveInstanceState() called, storing last page index 0
D/ListFragment﹕ [1106541040] onSaveInstanceState() called, storing last page index 0
D/ListFragment﹕ [1108316656] onSaveInstanceState() called, storing last page index 0
D/ListFragment﹕ [1109134136] onSaveInstanceState() called, storing last page index 0
D/ListFragment﹕ [1108630992] onSaveInstanceState() called, storing last page index 0
D/ListFragment﹕ [1108592888] onSaveInstanceState() called, storing last page index 0
D/ListFragment﹕ [1109729064] onSaveInstanceState() called, storing last page index 0
D/ListFragment﹕ [1110257048] onDestroy()
D/ListFragment﹕ [1110257048] onDetach()
D/ListFragment﹕ [1109835992] onDestroy()
D/ListFragment﹕ [1109835992] onDetach()
D/ListFragment﹕ [1108826176] onDestroy()
D/ListFragment﹕ [1108826176] onDetach()
D/ListFragment﹕ [1108083096] onDestroy()
D/ListFragment﹕ [1108083096] onDetach()
D/ListFragment﹕ [1106541040] onDestroy()
D/ListFragment﹕ [1106541040] onDetach()
D/ListFragment﹕ [1108316656] onDestroy()
D/ListFragment﹕ [1108316656] onDetach()
D/ListFragment﹕ [1109134136] onDestroy()
D/ListFragment﹕ [1109134136] onDetach()
D/ListFragment﹕ [1108630992] onDestroy()
D/ListFragment﹕ [1108630992] onDetach()
D/ListFragment﹕ [1108592888] onDestroy()
D/ListFragment﹕ [1108592888] onDetach()
D/ListFragment﹕ [1109729064] onDestroy()
D/ListFragment﹕ [1109729064] onDetach()
D/ListFragment﹕ [1110903656] onAttach()
D/ListFragment﹕ [1110903656] onCreate()
D/ListFragment﹕ [1110903656] savedInstanceState is not NULL.
D/ListFragment﹕ [1110903656] Retrieving last page index 3
D/ListFragment﹕ [1110905248] onAttach()
D/ListFragment﹕ [1110905248] onCreate()
D/ListFragment﹕ [1110905248]   savedInstanceState is not NULL.
D/ListFragment﹕ [1110905248]   Retrieving last page index 0
D/ListFragment﹕ [1110906440] onAttach()
D/ListFragment﹕ [1110906440] onCreate()
D/ListFragment﹕ [1110906440]   savedInstanceState is not NULL.
D/ListFragment﹕ [1110906440]   Retrieving last page index 0
D/ListFragment﹕ [1110907632] onAttach()
D/ListFragment﹕ [1110907632] onCreate()
D/ListFragment﹕ [1110907632]   savedInstanceState is not NULL.
D/ListFragment﹕ [1110907632]   Retrieving last page index 0
D/ListFragment﹕ [1110908824] onAttach()
D/ListFragment﹕ [1110908824] onCreate()
D/ListFragment﹕ [1110908824]   savedInstanceState is not NULL.
D/ListFragment﹕ [1110908824]   Retrieving last page index 0
D/ListFragment﹕ [1110910016] onAttach()
D/ListFragment﹕ [1110910016] onCreate()
D/ListFragment﹕ [1110910016]   savedInstanceState is not NULL.
D/ListFragment﹕ [1110910016]   Retrieving last page index 0
D/ListFragment﹕ [1110911208] onAttach()
D/ListFragment﹕ [1110911208] onCreate()
D/ListFragment﹕ [1110911208]   savedInstanceState is not NULL.
D/ListFragment﹕ [1110911208]   Retrieving last page index 0
D/ListFragment﹕ [1110912400] onAttach()
D/ListFragment﹕ [1110912400] onCreate()
D/ListFragment﹕ [1110912400]   savedInstanceState is not NULL.
D/ListFragment﹕ [1110912400]   Retrieving last page index 0
D/ListFragment﹕ [1110913592] onAttach()
D/ListFragment﹕ [1110913592] onCreate()
D/ListFragment﹕ [1110913592]   savedInstanceState is not NULL.
D/ListFragment﹕ [1110913592]   Retrieving last page index 0
D/ListFragment﹕ [1110914784] onAttach()
D/ListFragment﹕ [1110914784] onCreate()
D/ListFragment﹕ [1110914784]   savedInstanceState is not NULL.
D/ListFragment﹕ [1110914784]   Retrieving last page index 0
D/HomeActivity﹕ fragment updated
D/ListFragment﹕ [1110914784] onCreateView()
D/ListFragment﹕ [1111031048] onAttach()
D/HomeActivity﹕ Fragment attached.
D/ListFragment﹕ [1111031048] onCreate()
D/ListFragment﹕ [1111031048]   savedInstanceState is NULL.
D/ListFragment﹕ [1111031048] onCreateView()
D/ListFragment﹕ [1111031048] onResume(), restoring page index 0

Это журнал после того, как я повернул экран около 10 раз. Число в теге — это классы hashCode(). Строки выше показывают, что onSaveInstanceState() и onCreate() из ранее созданных фрагментов по-прежнему вызываются даже после того, как они заменены последним (1111031048).

Обратите внимание, что я не вызывал setRetainInstance() в классе фрагментов. На самом деле, я пробовал и setRetainInstance(false), и setRetainInstance(true), но это ничего не меняет.

Я сделал что-то не так здесь? Я понимаю, что ListFragment нужно воссоздать, но почему savedInstanceState равно null? И если это ожидаемое поведение, как правильно решить мою задачу, т.е. сохранить индекс страницы при изменении конфигурации?

Должна быть возможность сделать индекс страницы статической переменной класса, но я не уверен, действительно ли это решает проблему или просто скрывает ее (потому что я чувствую утечку памяти в журнале выше).


person Edmund Tam    schedule 24.01.2014    source источник
comment
Вызываете ли вы super для всех переопределенных действий и методов фрагментов (таких как onCreate, onSaveInstanceState) и т. д.?   -  person Ari    schedule 24.01.2014
comment
Вы уверены, что ваш фрагмент, содержащий ViewPager, не добавляется снова (по ошибке) вашим кодом после поворота, заменяя сохраненный?   -  person user    schedule 24.01.2014
comment
@Luksprog Вы абсолютно правы. Я слишком сосредоточился на фрагменте и проглядел эту часть. Я обновлю свой пост. Большое спасибо!   -  person Edmund Tam    schedule 24.01.2014
comment
Вы должны опубликовать ответ и принять его (также и для других ваших вопросов, если вы получили правильный ответ), чтобы вопрос отображался как решенный.   -  person user    schedule 25.01.2014
comment
Спасибо. Я не был уверен, уместно ли мне отвечать на мой собственный вопрос (и помечать его как ответ) из-за такой глупой ошибки, но я последовал вашему совету и надеюсь, что это скоро кому-то поможет.   -  person Edmund Tam    schedule 27.01.2014


Ответы (5)


Несмотря на то, что ответ уже принят, позвольте мне уточнить это еще раз: «проблема» заключается в шаблоне Android Studio. Проблема, как указал Эдмунд, заключается в том, что навигационный ящик вызывает меню при воссоздании, таким образом, повторно вызывая фрагмент. Чтобы решить эту проблему, я внес небольшую модификацию в файл NavigationDrawerFragment.java, предложенную Android Studio. Оригинал:

    @Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Read in the flag indicating whether or not the user has demonstrated awareness of the
    // drawer. See PREF_USER_LEARNED_DRAWER for details.
    SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
    mUserLearnedDrawer = sp.getBoolean(PREF_USER_LEARNED_DRAWER, false);

    if (savedInstanceState != null) {
        mCurrentSelectedPosition = savedInstanceState.getInt(STATE_SELECTED_POSITION);
        mFromSavedInstanceState = true;
    }

    // Select either the default item (0) or the last selected item.
    selectItem(mCurrentSelectedPosition);

}

Новый:

    @Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Read in the flag indicating whether or not the user has demonstrated awareness of the
    // drawer. See PREF_USER_LEARNED_DRAWER for details.
    SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity());
    mUserLearnedDrawer = sp.getBoolean(PREF_USER_LEARNED_DRAWER, false);

    if (savedInstanceState != null) {
        mCurrentSelectedPosition = savedInstanceState.getInt(STATE_SELECTED_POSITION);
        mFromSavedInstanceState = true;
    } else {
        // Select either the default item (0) or the last selected item.
        selectItem(mCurrentSelectedPosition);
    }
}

Просто потерял половину производственного дня, чтобы понять это. Надеюсь, это может помочь кому-то еще.

person Miguel El Merendero    schedule 23.10.2014
comment
Это очень помогло мне. Я пытался понять, как предотвратить воссоздание фрагмента при повороте экрана. Спасибо! - person Eugene H; 01.04.2015
comment
Спасибо ооооочень большое - person zaerymoghaddam; 18.08.2015
comment
Великолепно! Это именно то, что я искал. Интересно, почему встроенный навигационный ящик не полностью подготовлен к изменениям конфигурации. Это помогло! Спасибо! - person crubio; 21.08.2015

Как обновлено в вопросе, это решено, и еще раз спасибо @Luksprog за указание на то, что я упустил из виду.

Поведение Fragment фактически совпадает с Activity классами.

Вот причина моей проблемы:

  • Проект создается с использованием шаблона навигационного ящика, предоставляемого мастером создания проекта Android Studio. Ящик реализован в классе NavigationDrawerFragment.
  • Фрагмент, содержащий пейджер представления, добавляется при выборе определенного элемента в ящике. Код реализован моей домашней деятельностью.
  • При повороте экрана вызывается метод onCreate() из NavigationDrawerFragment, сохраняющий последний выбранный элемент.
  • И вот что пошло не так - при воссоздании NavigationDrawerFragment снова вызовет selectItem(), что вызовет обработчик выбранного пункта меню. Это приводит к замене ListFragment.

Этого можно избежать, проверив активный пункт меню в моем коде обработчика выбора меню или отключив этот вызов selectItem().

person Edmund Tam    schedule 27.01.2014

Вам не нужно обрабатывать saveInstanceState. Поскольку все фрагменты хранятся в диспетчере фрагментов (это как массив фрагментов), и при изменении ориентации действие уничтожается, но диспетчер фрагментов по-прежнему удерживает все фрагменты, которые были добавлены ранее. Поэтому вам просто нужно проверить, был ли уже добавлен фрагмент.

private void openFragment(Fragment fragment, String tag) {
            FragmentManager fragmentManager = getSupportFragmentManager();
            FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
            Fragment existingFragment = fragmentManager.findFragmentByTag(tag);
            if (existingFragment != null) {
                fragmentTransaction.add(R.id.container, fragment, tag);
                fragmentTransaction.commit();
            }
        }
person Dmytro Ubogyi    schedule 18.05.2018

Попробуйте использовать здесь onCreateView() вместо onCreate(), что характерно для фрагментов и может иметь значение.

Переопределите onSaveInstanceState(), чтобы сохранить ваше состояние, и восстановите его в onCreateView() с помощью savedInstanceState.get*(). Вот как мы делаем это в наших приложениях, и это должно работать. Как уже упоминал @Ari, обязательно позвоните super.onSaveInstanceState(outState).

class MyFragment extends ListFragment {

    @Override
    public void onSaveInstanceState(Bundle outState) {
        outState.putString(ARG_PAGE, page); 
        super.onSaveInstanceState(outState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        if(savedInstanceState != null){
            if(savedInstanceState.containsKey(ARG_PAGE)){
                page = savedInstanceState.getInteger(ARG_PAGE);
            }
        }
    }
}
person sulai    schedule 24.01.2014

В AndroidManifest.xml добавить

<activity android:name=".MainActivity"
android:configChanges="orientation|screenSize">
</activity>
person Sav4yk    schedule 03.02.2016