Android fragments and memory

This post was initially entitled "Android fragments - the important stuff". It will hopefully provide an overview of the less obvious things associated with utilizing fragments in Android.

What is the difference between attach/add and detach/remove?

Detaching a fragment essentially dissociates a fragment from its activity. The FragmentTransaction documentation states that "the fragment is removed from the UI". This is in my opinion confusing - I understand it to mean that the fragments UI is removed from the on-screen UI. The view is destroyed and is not held in memory. The view is destroyed so as to minimize the applications memory footprint.

The documentation also states "This is the same state as when it is put on the back stack" - that is to say the Fragment instance is stored in memory along with its instance variables etc.

Adding/Attaching a fragment is the opposite.

What am I adding to the backstack?

When you programatically add a fragment into a container, you do so using a FragmentTransaction. A fragment transaction is just a set of instructions for what you want to happen - remove fragment A, add fragment B for example.

When you add a transaction to the backstack, you do exactly that.. you add the transaction. The backstack contains FragmentTransactions not Fragments.

When you pop the backstack you essentially say 'do the opposite of this transaction' or 'do the opposite of the last x transactions'.

As such, when you add a transaction to the backstack using addToBackStack(String name) the name has nothing to do with the fragments involved. It is not a fragment tag or identifier. It is an identifier for the state after the transaction in question.

When you want to reverse the transactions, you use the various popBackstack() methods. More information can be found [here](http://developer.android.com/reference/android/app/FragmentManager.html#popBackStack(java.lang.String, int)).

Memory considerations

As commented in this StackOverflow question, "when a fragment is put on the back stack, its onDestroyView() is called. If at that point you clean up any major allocations being held by the fragment, you shouldn't have memory issues". This quote is vital for appreciating the memory considerations of fragments (and remember.. as mentioned above, this is the same as when you detach a fragment).

When you detatch a fragment, the following lifecycle methods are called: onPause(), onStop(), onDestroyView(), onDestroy(), and onDetach(). If for example you had saved a reference to a visible GoogleMap in an instance variable it would be worth destroying this in onDestroyView() and recreating it if/when you attach the fragment once again and its onCreateView() method is called.

What about show/hide?

FragmentTransaction does have two additional methods - show and hide. I added these to my demo so you can see for yourself that these do exactly what they say on the tin.. they show and hide. No lifecycle methods are called, and every aspect of the fragment instance is held in memory (including the view heirachy).

If you create a lot of fragments, hide them, and add new ones on top (why you would do this I do not know), you will run out of memory sooner or later.

If you have a fragment which has an expensive view heirachy.. and you want it to be off screen for 3 seconds.. these methods may be of use.

Trying it out

The demo application (linked above) allows you to play with these various fragment transactions and view the lifecycle in your logcat output.

What you will notice Is that if you 'Add' a fragment, then 'Remove' it, and then try to 'Attach' the same fragment nothing will happen.

This is because whilst you hold a reference to the fragment, when you remove a fragment the FragmentManager gets rid of its reference to it.

As such when you try and attach it, nothing happens.

Below I have outlined the lifecycle of the various approaches to using fragments. This should outline what happens, and why. With this knowledge you can use the most appropriate methods for your use case.

The lifecycle

1. Add

If you press the add button in the demo, these lifecycle methods are called.

We set an instance variable with the current time to demonstrate how state is saved.

onAttach
onCreate
onCreateView
onActivityCreated
onStart
onResume

Pop

If we had 'Add to backstack' checked, and then pressed 'Pop', these lifecycle methods are called.

onPause
onStop
onDestroyView
onDestroy
onDetach

2. Remove

If you have added the fragment, and then remove it.. these lifecycle methods are called.

onPause
onStop
onDestroyView
onDestroy
onDetach

Pop

Popping this will re-add the view. As you can see the view is recreated (onCreateView is called) - when removed the view is destroyed competely. Even when the transaction is added to the backstack.

You will however see that the system time outputted is the same. This is because when removed, if added to the backstack the fragment is maintained in a state similar to when it is detatched - its instance variables are retained.

If you were to simply add the fragment, remove it, and then press add again, the system time would be different as under this condition the state is not retained. The full lifecycle (as in 1.) would be executed.

onCreateView
onActivityCreated
onStart
onResume

3. Detach

If you have pressed 'Add', and then click 'Detach' the following lifecycle methods are executed.

onPause
onStop
onDestroyView

Pop

If at this point you had had addToBackstack checked and you now pop you will see the following lifecycle methods called. You will note that the view is recreated and that the system time is the same.

In fact this is the exact same as a 'post-remove-pop'.

onCreateView
onActivityCreated
onStart
onResume

4. Attach

If you attach a previously detached fragment, the below lifecycle methods are called.

Again you will notice that the system time is maintained.

onCreateView
onActivityCreated
onStart
onResume

Pop

If you attach a fragment, add it to the backstack and then pop it, the following lifecycle methods are called.

The difference between this, and a 'post-add-pop is that onDestroy and onDetach are not called.

The significance of this is that after popping you could press 'Attach' once again, and you would notice that the system time is the same.

On the other hand when you 'Add' a fragment, and pop it from the backstack it is completely destroyed - its view.. its instance variables.. everything.

onPause
onStop
onDestroyView

Are you having fun?

Because you are almost certainly having fun.. lets continue.

What the hell is setRetainInstance?!

The Fragment documentation states that it controls "whether a fragment instance is retained across Activity re-creation".

You can play with this in the demo by uncommenting the appropriate line in ExampleFragment.

What it essentially amounts to is that if you add a fragment, and then rotate your screen the following lifecycle methods are called:

onPause
onStop
onDestroyView
onDestroy
onDetach

onAttach
onCreate
onCreateView
onActivityCreated
onStart
onResume

If you hadn't noticed, this happens to be the same lifecycle as 'removing' a fragment, and then 'adding' it. Guess why? Because that is what is happening :)

What you will notice is that the system time output when it is readded is different to when it was initially added the fragment.

However this is expected.. after all we can see from the lifecycle that the fragment is completely destroyed before it is readded.

Now uncomment the setRetainInstance(true) line in the demo code and do the same thing again.

This time you will see the following lifecycle executed:

onPause
onStop
onDestroyView
onDetach
onAttach
onCreateView
onActivityCreated
onStart
onResume

The important difference is that onDestroy and onCreate are not called. The fragment is not destroyed and recreated but rather its view is destroyed, and state maintained.

You still need to rebuild the view (for all the memory related reasons outlined previously) but the fragment state is retained. The output system time is the same as when it was initially created.

If you have written a beautiful sonnet and stored it in an instance variable (who hasn't..) then you will find that it is stil there when you rotate your screen if setRetainInstance is passed a boolean true.

How does the SDK use this?

The Android SDK uses this stuff everywhere. Fragments are after all a very clever design pattern (if you can call them that).

FragmentTabHost and FragmentStatePagerAdapter are the two which instantly spring to mind.

Given the above you can adapt these classes depending on your needs. For example, if you want a tab host which hosts two fragments that have complex view heirachies yet are regularly changed, you might choose to modify the provided implementation. FragmentTabHost uses attach and detach within its doTabChanged implementation - you could change this to simply show/hide your tabs.

FragmentStatePagerAdapter uses add/remove exclusively, as well as an interesting caching mechanism. This is pretty logical really - if you had a lot of fragments in your pager, you certainly wouldn't want to keep all their views in memory at any given time.

Summary

Fragments seem more complex than they due to confusing documentation and terminology. Furthermore because of the large number of combinations of approaches for different use cases it is pretty difficult to visualise and process what happens and why.

Hopefully the demo app will help you get your head around the concepts, and hopefully my explanations make sense.

If anything needs clarifying or you have any questions.. ask away :)