Android: Hunting Memory Leak in Nested Fragments inside ViewPager

http://goo.gl/YP8f5f

Last week I have responsibility to develop an awesome feature for my company, Coral. This feature requires a dynamic content. Basically, the layout component looks like this.

The amount of the tab is dynamic. So, i use ViewPager with FragmentStatePagerAdapter for it. I leave the Offscreen Page Limit to its default, which is one. The content of ViewPager is Parent Fragment that could contains many Child Fragment. Every tab has the same content, Parent Fragment and Child Fragment.

Everything worked fine. Until one time I checked the memory monitor while swiping from one tab to another tab many times. And WOW, the memory usage was steadily increasing and was never decreasing! Oh, it must be a memory leak! But, why did it happen?

First thing that came in mind was references to Child Fragment. Maybe there are references to Child Fragment that never be cleared. So, I cleared all possibility references to Child Fragment. I did it by resetting/removing all callback to Child Fragment.

I ran the test again. I swiped from one tab to another tab many times like before. I saw memory monitor while doing the test. The memory usage was still steadily increasing. Even the increasing was not as high as before. Well, I concluded that clearing references did not solve the problem yet. So, I added log inside onDetach in Child Fragment.

@Override
public void onDetach() {
Log.d(TAG, "onDetach");
super.onDetach();
}

I wanted to check that Child Fragment is supposed to be detached if it is not on the current tab, previous tab, or next tab. Then, I ran the test again. And guess what? Somehow onDetach in Child Fragment was never called! This was strange. So, I came back to the code, and looked for any potential root cause. Then I suspected this.

getActivity().getSupportFragmentManager()
.beginTransaction()
.replace(frameLayout.getId(), childFragment, tag)
.commitAllowingStateLoss();

The above code was used for adding the Child Fragment in Parent Fragment. I asked myself, is this the right method to add Child Fragment inside Parent Fragment? I searched on stackoverflow and found that someone referred to this. It says,

To nest a fragment, simply call getChildFragmentManager() on the Fragment in which you want to add a fragment.

That is the important part! Aha, that is solution! Since I implemented Nested Fragment, so I need to change the fragment addition code into this.

getChildFragmentManager()
.beginTransaction()
.replace(frameLayout.getId(), childFragment, tag)
.commitAllowingStateLoss();

Alright, I ran the test again. I watched memory monitor while doing the test. And Yeay! The memory usage was decreasing 🏂. It means the garbage collector ran well. But wait, the freed memory is still less than the used memory. Hmm, Okay, I needed to analyse the memory usage. So, I decided to check the memory usage using Eclipse Memory Analyzer (MAT).

MAT is a useful tool to analyse your memory usage. Before analyse the memory usage, first we need to create the .hprof file from my activities on the app. We can build this .hprof file easily using Android Studio. Here are the steps:

1. Run the test on your app. Do something that potentially can cause memory leak.

2. Initiate GC (Garbage Collector)

3. Dump java heap

4. Open heap snapshot in capture tab on the left side. Then, export the snapshot to standard .hprof.

5. That’s it. Now we have the standard .hprof file. Open this file using MAT. Then start analyse. You can find how to analyse your memory usage using MAT here.

Ok let’s get back to the case. After I created and analysed the .hprof file, I found out that there are many Child Fragment objects was trapped in Child Fragment Manager. How could it happen? Why Android is not releasing them even after onDetach is called? Well, alright. I needed to release the Child Fragment objects manually.

So, the idea was removing Child Fragment objects that belong to Parent Fragment from Child Fragment Manager before added them. So, if I have Parent A with Child 1, Child 2, and Child 3, I need to remove Child 1, Child 2, and Child 3 from Child Fragment Manager before i added them to Parent A.

To achieve the idea, I created map object to store the tag of Child Fragment that was added to Parent Fragment.

private static final Map<String, List<String>> CHILD_FRAGMENT_TAGS_MAP = new HashMap<>();

This CHILD_FRAGMENT_TAGS_MAP is belong to Parent Fragment. Every time Child Fragment was added to Parent Fragment, I stored Child Fragment’s tag to that map. Every Parent Fragment has a unique id. So, I used parent’s id as the key.

Then, I cleared all previous Child Fragment objects that belong to Parent Fragment before added them.

That’s it. After added some modifications, I ran the test again. I watched memory monitor as well while doing the test. And guess what? The freed memory was the same as used memory! So, the memory usage was not steadily increasing! To make it more sure, I built the .hprof file and analysed it. After analysed, finally I could saw the Child Fragment objects is at the right amount. Yeay! Awesome! \:D/


All in all, memory leak in nested fragments inside view pager can be avoided by:

  • Clearing references to Child Fragment.
  • Using Child Fragment Manager instead of Fragment Manager to add nested fragment.
  • Removing Child Fragment that belongs to Parent Fragment from Child Fragment Manager before added it to Parent Fragment.

**You could get the example of this case here.