Fighting memory leaks in Android. Part 1

    With this article we open a series of articles on Habré about our development for Android.
    According to a 2012 Crittercism report, OutOfMemoryError is the second leading cause of mobile app crashes.
    Honestly, in Badoo this error was at the top of all crashes (which is not surprising with the volume of photos that our users view). Fighting OutOfMemory is a painstaking task. We picked up the Allocation Tracker and started playing with the application. Observing the data of the reserved memory, we identified several scenarios in which the allocation of memory grew with suspicious speed, while forgetting to decrease. Armed with several memory dumps after these scenarios, we analyzed them in MAT ( http://www.eclipse.org/mat/ ).
    The result was entertaining and allowed us to reduce the number of crashes at times within a few weeks. Something was specific to our code, but it also revealed typical problems inherent in most Android applications.
    Today we’ll talk about a specific case of memory leak. Many people know about him, but often turn a blind eye to it (but in vain).

    We will talk about memory leaks associated with the misuse of android.os.Handler. It is not entirely obvious, but everything that you put in the Handler is in memory and cannot be cleared by the garbage collector for some time. Sometimes quite long.
    A little later we will show with examples what is happening and why memory cannot be freed. If you are not curious, but want to know how to deal with the problem, then go to the conclusions at the end of the article. Or immediately go to the page of a small library, which we put in open access: https://github.com/badoo/android-weak-handler .

    So, what is “flowing" there? Let's figure it out.

    Simple example




    This is a very simple Activity class. Suppose we need to change the text after 800 seconds. An example, of course, is ridiculous, but it will show us well how the streams of our memory flow.
    Pay attention to the anonymous Runnable, which we post in Handler. It is also important to pay attention to the long timeout.
    For the test, we ran this example and turned the phone 7 times, thereby causing a change in screen orientation and recreation of Activity. Then they took a memory dump and opened it in MAT ( http://www.eclipse.org/mat/ ).

    Using OQL, we run a simple query that displays all instances of the Activity class:

    select * from instanceof android.app.Activity
    

    We highly recommend reading about OQL - it will significantly help you in memory analysis.
    You can read here visualvm.java.net/oqlhelp.html or here help.eclipse.org/luna/index.jsp?topic=%2Forg.eclipse.mat.ui.help%2Freference%2Foqlsyntax.html .



    There are 7 Activity instances in memory. This is 7 times more than necessary. Let's see why the garbage collector could not remove the spent objects from memory. Let's open the shortest graph of links to one of the Activities:



    The screenshot shows that this $ 0 refers to the Activity. This is an implicit reference from an anonymous class to an external class. In Java, any anonymous class always has an implicit reference to an external class, even if you never access external fields or methods. Java is not perfect, and life is a pain. Such things, kittens.

    Next, the reference to this $ 0 is stored in a callback , which is stored in a linked message list. At the end of the chain is a local link in the stack of the main thread. Apparently, this is a local variable in the main loop of the UI thread, which will be freed when the cycle is completed. In our case, this will happen after the application completes its work.

    So, after we put Runnable or Message in the Handler, it will be stored in the message list in LooperThread until the message works. It is quite obvious that if we place a pending message, it will remain in memory until its time comes. Together with the message in memory all objects to which the message refers will lie explicitly and implicitly.
    And you need to do something about it.

    Static Class Solution


    Let's try to solve our problem by getting rid of this $ 0 link . To do this, we transform the anonymous class into a static one:



    Run it, rotate the phone a couple of times and collect a memory dump.



    More than one Activity again? Let's see why the garbage collector could not remove them.



    Pay attention to the bottom of the link graph: Activity is stored in the mContext link from mTextView inside the DoneRunnable class. Obviously, using a static class alone is not enough to avoid a memory leak. We need to do something else.

    Solution Using Static Class and WeakReference


    We continue the sequential method of getting rid of the TextView link that we found while studying memory dumps.



    Please note that we keep the link to the TextView in WeakReference. Using WeakReference requires special care: such a link can be reset at any time. Therefore, we first save the link to a local variable and work only with the latter, checking it for null.

    Run, rotate and collect a memory dump.



    We have achieved the desired! Only one Activity in mind. The problem is resolved.

    To use this approach, we need:

    • Use a static inner or outer class
    • use WeakReference for all the objects we refer to.

    Is this method good?
    If you compare the original code and the "safe" code, then a large amount of "noise" is striking. It distracts from understanding the code and complicates its support. Writing such code is still a pleasure, not to mention the fact that you can forget or score something.

    It’s good that there are better solutions.

    Clear all messages in onDestroy


    The Handler class has an interesting and very useful method - removeCallbacksAndMessages , which takes null as an argument. It deletes all messages in the queue of this Handler. Let's use it in onDestroy.



    Run, rotate and remove the memory dump.



    Perfectly! Only one Activity class.

    This method is much better than the previous one: the amount of accompanying code is minimal, the risks of making a mistake are much lower. One trouble - do not forget to call for cleaning in the onDestroy methods or where you need to clean the memory.

    We have one more method in stock that you might like a lot more.

    Solution Using WeakHandler



    Badoo team wrote their Handler - WeakHandler . This is a class that behaves exactly like a Handler, but eliminates memory leaks.

    It uses soft and hard links to avoid memory leaks. We will describe the principle of its operation a little later, but for now let's take a look at the code:



    It is very similar to the original code, right? Only one small detail: instead of using android.os.Handler, we used WeakHandler . Let's start, rotate the phone several times and remove the memory dump.



    At last! The code is clean as a tear and memory does not flow.

    If you liked this method, here is the good news: using WeakHandler is very simple.

    Add maven dependency to your project:
    repositories {
        maven {
            repositories {
                url 'https://oss.sonatype.org/content/repositories/releases/'
            }
        }
    }
    dependencies {
        compile 'com.badoo.mobile:android-weak-handler:1.0'
    }
    


    Import WeakHandler in your code:

    import com.badoo.mobile.util.WeakHandler
    

    The source code is posted on github: github.com/badoo/android-weak-handler .

    How WeakHandler Works


    The main idea is to keep a hard link to posts or Runnable as long as there is a hard link to WeakHandler . As soon as the WeakHandler can be deleted from memory, everything else must be deleted with it.

    For simplicity of explanation, we will show a simple diagram showing the difference between putting anonymous Runnable in a simple Handler and in WeakHandler :



    Notice the top diagram: Activity refers to the Handler that posts the Runnable (puts it in the message queue that Thread refers to). Everything is fine, except for the implicit backlink from Runnable to Activity. As long as Message is in the queue that lives, while Thread is alive, the entire graph cannot be collected by the garbage collector. Including a thick Activity.

    In the bottom diagram, Activity refers to the WeakHandler that holds the Handler inside. When we ask him to put Runnable, he wraps it in WeakRunnable and posts it to the queue. Thus, the message queue refers only to WeakRunnable. WeakRunnable contains a WeakReference to the original Runnable, i.e. The garbage collector can clean it at any time. So that he does not clear it ahead of time, WeakHandler keeps a hard link to Runnable. But as soon as WeakHandler itself can be removed, Runnable can also be removed.

    You need to be careful and not to forget that the WeakHandler should be referenced from the outside, otherwise all messages will be cleared with it by the garbage collector.

    conclusions


    Using postDelayed in Android is not as simple as it seems: you need to take additional steps so that the memory does not flow. To do this, you can use the following methods:

    • use the static inner class Runnable / Handler with WeakReferences to the outer class;
    • clear all messages in the Handler class from the onDestroy method;
    • use WeakHandler from Badoo ( https://github.com/badoo/android-weak-handler ).

    The choice is yours. The first method is definitely not for the lazy. The second method looks quite simple, but requires additional work. The third one is our favorite, but you need to be careful: WeakHandler should have an external link until you need it, otherwise the garbage collector will delete it along with all messages from the queue.

    Good luck to you! Stay with us - this topic will be continued.

    An article on our English-language blog: bit.ly/AndroidHandlerMemoryLeaks

    Dmitry Voronkevich, lead developer

    Also popular now: