Android fragment retain instance

Сохранение состояния фрагментов (Fragment)

Распространенной проблемой является некорректное поведение приложения при повороте девайса. Дело в том что при повороте Activity-host (Activity которое является родителем для фрагмента) уничтожается. В тот момент когда этот процесс происходит FragmentManager отвечает за уничтожение дочернего фрагмента. FragmentManager запускает методы угасающего жизненного цикла фрагмента: onPause(), onStop() и onDestroy().

В случае если в контроллере нашего дочернего фрагмента, к примеру, есть объект Media-Player, то в методе фрагмента Fragment.onDestroy() экземпляр нашего звонко играющего Media-Player-а прервет воспроизведение медиа данных. Первое, что приходит в голову, сохранить состояние объекта Media-Player вызвав Fragment.onSaveInstanceState(Bundle), что сохранит данные, а новое Activity загрузит их. Однако, сохранение состояния объекта MediaPlayer и его последующее восстановление, все равно, прерывает воспроизведение и заставляет акул ненависти сновать в головах пользователей.

Сохранение Fragments

Благо, у Fragment присутствует механизм, при помощи которого экземпляр Media-Player может “пережить” изменение конфигурации. Переопределив метод Fragment.onCreate(. ) и задав свойство фрагмента.

Свойство retainInstance фрагмента, по дефолту, является false. Это означает, что при поворотах девайса Fragment не сохраняется, а уничтожается и создается по новому вместе с Activity-host. При вызове setRetainInstance(true) фрагмент не уничтожается вместе с его хостом и передается новому Activity в не измененном виде. При сохранении фрагмента можно рассчитывать на то, что все его поля (включая View) сохранят прежние значения. Мы к ним обращаемся, а они уже есть и все. Используя этот подход можно убедится в том, что при повороте девайса наш объект MediaPlayer не прервет свое воспроизведение и пользователь не будет психовать.

Повороты и сохраненные фрагменты

Теперь стоит взглянуть более подробно на то, как работают сохраненные фрагменты. Fragment руководствуется тем обстоятельством, что представление фрагмента может уничтожаться и создаваться заново без необходимости уничтожать сам Fragment. При изменении конфигурации FragmentManager уничтожает и заново собирает представление фрагментов. Аналогично ведет себя и Activity. Это происходит из соображений что в новой конфигурации могут потребоваться новые ресурсы. На случай, если для нового варианта существуют более подходящие ресурсы, представление строится «с нуля».

FragmentManager проверяет свойство retainInstance каждого фрагмента. Если оно дефолтное (false), FragmentManager уничтожает экземпляр фрагмента. Fragment и его представление будут созданы заново новым экземпляром FragmentManager принадлежащем новой активности.

Что же происходит если значение retainInstance равно true. Представление фрагмента уничтожается но сам фрагмент остается. Создастся новый экземпляр Activity, а затем и новый FragmentManager который найдет сохраненный Fragment и воссоздаст его View.

Наш сохраненный фрагмент открепляется (detached) от предыдущей Activity и продолжает жить но уже не имея Activity-host.

Соответственно, переход в сохраненное состояние происходит при выполнении следующих условий:

  • для фрагмента был вызван метод setRetainInstance(true)
  • Activity-host уничтожается для изменения конфигурации (обычно это поворот девайса)

В этом случае Fragment живет совсем недолго от момента отсоединения от своей первой Activity до передачи его в пользование к немедленно созданной нового Acticvity.

Сохранение фрагментов: действительно так хорошо?

Сохраненные фрагменты: ведь правда, удобно? Да! Действительно удобно. На первый взгляд они решают все проблемы, возникающие с уничтожением Activity и фрагментов при поворотах. При изменении конфигурации устройства для подбора наиболее подходящих ресурсов создается новое представление, а в вашем распоряжении имеется простой способ сохранения данных и объектов.

В таком случае возникает вопрос, почему не сохранять все фрагменты подряд и почему фрагменты не сохраняются по умолчанию? Невольно складывается впечатление, что Android без энтузиазма относится к сохранению Fragment-ов в UI. Мне не ясно, почему это так.
Стоит отметить, что сохраненный Fragment продолжает существовать только при уничтожении Activity — при изменения конфигурации. Если Activity уничтожается из-за того, что ОС потребовалось освободить память, то все сохраненные фрагменты также будут уничтожены.

Читайте также:  Emulator on android pokemon

Пора поговорить и об onSaveInstanceState(Bundle)

Такой подход является более распространенным в борьбе с проблемой потери данных при поворотах. Если ваше преложение с легкостью отрабатывает эту ситуацию то все благодаря работе поведения onSaveInstanceState(. ) по дефолту.

Метод onSaveInstanceState(. ) проектировался для решения сохранения и восстановления состояния пользовательского интерфейса приложения. Как я думаю, Вы догадались — есть основополагающее различие между этими подходами. Главное отличие между переопределением Fragment.onSaveInstanceState(. ) и сохранением Fragment-а — продолжительность существования сохраненных данных.

В том случае, если вы преследуете цель сохранить данные на то мгновение пока происходит изменение конфигурации, сохранение фрагмента потребует существенно меньшей работы.Это особенно справедливо при сохранении объект- разработчику не нужно беспокоиться о том, реализует ли объект Serializable.

Но в том случае, если данные должны существовать дольше, сохранение фрагмента не поможет. Если Activity уничтожается для освобождения памяти после бездействия юзера, все сохраненные фрагменты уничтожаются так же, как и их не сохраненные родственники.

Источник

Handling Configuration Changes

There are various situations such as when the screen orientation is rotated where the Activity can actually be destroyed and removed from memory and then re-created from scratch again. In these situations, the best practice is to prepare for cases where the Activity is re-created by properly saving and restoring the state.

If a user navigates to a different away from the activity, the onPause() and onResume() methods are called. If you need to retain state information in those cases, it’s best to save state through the use of Shared Preferences:

Upon resume, the onResume() gets called:

Note that onSaveInstanceState() is called right before your activity is about to be killed or restarted because of memory pressure or screen orientation. This is different from onPause() which gets called when your activity loses focus (i.e. you transition to another activity). The default implementation of this method automatically saves information about the state of the activity’s view hierarchy, such as the text in an EditText widget or the scroll position of a ListView . For other data to persist, you can put the data in the Bundle provided.

The system will call that method before an Activity is destroyed. Then later the system will call onRestoreInstanceState where we can restore state from the bundle:

Instance state can also be restored in the standard Activity#onCreate method but it is convenient to do it in onRestoreInstanceState which ensures all of the initialization has been done and allows subclasses to decide whether to use the default implementation. Read this stackoverflow post for details.

Note that onSaveInstanceState and onRestoreInstanceState are not guaranteed to be called together. Android invokes onSaveInstanceState() when there’s a chance the activity might be destroyed. However, there are cases where onSaveInstanceState is called but the activity is not destroyed and as a result onRestoreInstanceState is not invoked.

Read more on the Recreating an Activity guide.

Fragments also have a onSaveInstanceState() method which is called when their state needs to be saved:

Then we can pull data out of this saved state in onCreateView :

For the fragment state to be saved properly, we need to be sure that we aren’t unnecessarily recreating the fragment on configuration changes. This means being careful not to reinitialize existing fragments when they already exist. Any fragments being initialized in an Activity need to be looked up by tag after a configuration change:

Читайте также:  Uber android что это

This requires us to be careful to include a tag for lookup whenever putting a fragment into the activity within a transaction:

With this simple pattern, we can properly re-use fragments and restore their state across configuration changes.

In many cases, we can avoid problems when an Activity is re-created by simply using fragments. If your views and state are within a fragment, we can easily have the fragment be retained when the activity is re-created:

This approach keeps the fragment from being destroyed during the activity lifecycle. They are instead retained inside the Fragment Manager. See the Android official docs for more information.

Now you can check to see if the fragment already exists by tag before creating one and the fragment will retain it’s state across configuration changes. See the Handling Runtime Changes guide for more details.

Often when you rotate the screen, the app will lose the scroll position and other state of any lists on screen. To properly retain this state for ListView , you can store the instance state onPause and restore onViewCreated as shown below:

Check out this blog post and stackoverflow post for more details.

Note that you must load the items back into the adapter first before calling onRestoreInstanceState . In other words, don’t call onRestoreInstanceState on the ListView until after the items are loaded back in from the network or the database.

Often when you rotate the screen, the app will lose the scroll position and other state of any lists on screen. To properly retain this state for RecyclerView , you can store the instance state onPause and restore onViewCreated as shown below:

Check out this blog post and stackoverflow post for more details.

If you want to lock the screen orientation change of any screen (activity) of your android application you just need to set the android:screenOrientation property of an within the AndroidManifest.xml :

Now that activity is forced to always be displayed in «portrait» mode.

If your application doesn’t need to update resources during a specific configuration change and you have a performance limitation that requires you to avoid the activity restart, then you can declare that your activity handles the configuration change itself, which prevents the system from restarting your activity.

However, this technique should be considered a last resort when you must avoid restarts due to a configuration change and is not recommended for most applications. To take this approach, we must add the android:configChanges node to the activity within the AndroidManifest.xml :

Now, when one of these configurations change, the activity does not restart but instead receives a call to onConfigurationChanged() :

See the Handling the Change docs. For more about which configuration changes you can handle in your activity, see the android:configChanges documentation and the Configuration class.

Android’s new Architecture Components helps manage configuration states. All state data related to the UI can be moved to a ViewModel, which will survive rotation changes because they are data tied to the application instead of Activity instance. In this way, you do not need to worry about persisting this configuration data between Activity lifecycles.

First, make sure to add the ViewModel library:

Your ViewModel should extend from the ViewModel class. Move for instance the adapter to be encapsulated within the ViewModel:

Читайте также:  Android одевалки для девочек

Next, create a ViewModel class:

Next, change your references to the adapter to refer to the ViewModel:

Источник

MichaЕ‚ ЕЃuszczuk

Personal thoughts about code, Android and more

Android Fragments restoration mechanism

In my last post, where I described a problem of incorrect usage of Fragments instantiation inside FragmentPageAdapter & ViewPager , I wrote:

After orientation change all fragments currently added to FragmentManager are automatically restored and instantiated so there is no need to create them once again.

Today, I want to focus more specifically on an issue how this automatic restoration works under the hood.

Beginning

The simplest way to use a fragment:

A custom fragment instance is created and added to FragmentManager with help of FragmentTransaction .
We could add that our Fragment will be added to container identified by android.R.id.content .

This is the easiest case. I want to point out that we are not using setRetainInstance(true) inside our custom fragment implementation, and our activity is not protected in manifest agains any type of configuration changes.

The Question

Now, what will happen with our fragment if suddenly our device configuration will change, i.e. orientation?

Android Source Code (especially FragmentActivity and FragmentManager / FragmentManagerImpl ) is a place where we should look for the answer.

1. Saving the state

Before Activity will be destroyed its onSaveInstanceState method will be called. Take a look what this method is doing internally.

It takes mFragments (reference to FragmentManager held in this Activity) and calls FragmentManager.saveAllState() method which will return a parcelable object ready to be saved inside the bundle which, you can already guess… later will be used to restore Fragments.

In reality result of saveAllState method call is an object of type FragmentManagerState which consists of information about all active fragments and back stack entries

Last quick look at FragmentState .

It consists of all data which describes a specific fragment object instance, like container id, tag, arguments but also savedFragmentState.

It looks sufficient to create fragments from scratch, and set them like they were before.

2. Destroying fragments and activities.

After state of fragments is saved via FragmentManager activity object is destroyed (removed from memory) with all its fragments (those which are not retained with setRetainInstance(true) ).

3. Creating activity and recreating fragments

Final point is a recreation of the activity and recreation of the fragments. It starts within the first Activity lifecycle callback method onCreate(Bundle savedInstanceState)

The previously saved fragment manager state bundle now is used inside FragmentManager.restoreAllState method. This metod declaration is quite long but I want to focus on the most important part.

Array with all FragmentState objects is iterated. And every FragmentState object is used to recreate (create new instance with state like before) specific Fragments.

A new instance of a fragment is created by platform with usage of reflection and default constructor – that’s why you must remember to always ensure the existence of public non-argument fragment constructor and initialize your fragment through arguments bundle (not with usage of fragment object setters from strange places).

This is short story how restoration of Fragments works.

Conclusion

Points to remember:

  • Already created fragments are restored automatically after orientation change
  • Magic behind this is just code written in Activity together with FragmentManager logic/implementation
  • Avoid setter methods and parametrized constructors to modify fragment state, because platform uses only default (0 parameter) constructor, arguments bundle and saved state bundle to restore it later
  • Fragments are not so bad

Источник

Оцените статью