In this post I will share an example of analyzing memory leak in an Android application. I recently tried to integrate a popular android library ShowcaseView which can be used for first run demos in your android application. The thing is while testing the library, I noticed severe memory leaks. This was occurring because references to ShowcaseView class were being kept. And, as bitmaps are created internally by this class to overlay the content, so each instance was holding a large chunk of memory.
Analyzing the issue
For heap analysis, I used VisualVM. First, thing you need to realize is that the heap dump taken from android cannot be directly analyzed with VisualVM or other tools such as eclipse MAT. You need to convert it to a format that these tools can analyze. For this you have to run the following command.
1 | hprof-conv Snapshot_2015.04.10_19.37.29.hprof heapissue
|
Here the first parameter is the android heap dump and the second is the output file.
After loading the file into VisualVM for analysis, I could see the references of ShowCaseView class were being retained as it had GC roots, thus live references, which meant that the instances could not be collected by the JVM Garbage collector.
Simple OQL analysis revealed live paths to the instances.
Query:
1 | select heap.livepaths(u,false) from com.github.amlcurran.showcaseview.ShowcaseView u |
As you can see phone decor view was holding references to the view which was added by ShowCaseView. Thus, the decorview was holding reference to ShowCaseView instances.
Now, Ideally whenever the view has been displayed it should be destroyed, especially, if it is holding such large chunks of memory. but, instead due to oversight from the developer instead of removing the view from decorview, he was just setting the views property to View.Gone, which only makes the view invisible and does not remove the view from the ViewGroup.
The Solution
The problem was then clearly with the fact that the added views should be removed after the ShowcaseView was hidden. So, I just modified the hide() and hideImmediate() methods to remove the views that were added on top of the decorView.
I have mentioned one of those method below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | @Override public void hide() { clearBitmap(); // If the type is set to one-shot, store that it has shot shotStateStore.storeShot(); mEventListener.onShowcaseViewHide(this); fadeOutShowcase(); getViewTreeObserver().removeOnPreDrawListener(draw); if(Build.VERSION.SDK_INT>15){ getViewTreeObserver().removeOnGlobalLayoutListener(globalLayout); } else { getViewTreeObserver().removeGlobalOnLayoutListener(globalLayout); } // removeView(ShowcaseView.this); ((ViewGroup)mActivity.getWindow().getDecorView()).removeView(ShowcaseView.this); } |
Post, implementing these changes the library works as it is supposed to work, that is just fine.
Below, I have mentioned the Gist that contains the entire source code for the modified ShowcaseView. So, happy coding :-)