📜 ⬆️ ⬇️

Fight against 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 report from Crittercism, OutOfMemoryError is the second most common reason for crashing mobile apps.
To be honest, in Badoo this error was in the top of all the most beautiful (which is not surprising given the volume of photos that our users view). Fighting OutOfMemory is a painstaking exercise. We picked up the Allocation Tracker and started playing with the app. Observing the data of the reserved memory, we identified several scenarios in which the memory allocation grew with suspicious swiftness, 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 amount of beautiful at times over several weeks. Something was specific to our code, but also revealed typical problems inherent in most Android applications.
Today we will talk about a specific case of memory leak. Many people know about him, but often close their eyes to it (but in vain).

It will be about memory leaks associated with improper use of android.os.Handler. Not entirely obvious, but everything you put into 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 have laid out in open access: https://github.com/badoo/android-weak-handler .

So, what is there "flowing"? Let's see.

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 well demonstrate to us how the streams flow to our memory.
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 launched this example and turned the phone 7 times, thereby causing a change of screen orientation and the re-creation of the Activity. Then they took a memory dump and opened it in MAT ( http://www.eclipse.org/mat/ ).

Using OQL, we launch a simple query that displays all the 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 .



In memory hangs 7 instances of Activity. 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 Activity:



The screenshot shows that this $ 0 refers to Activity. This is an implicit link 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, but life is pain. Such things, the cat.

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

So, after we put Runnable or Message in Handler, it will be stored in the list of messages in LooperThread until the message runs out. Obviously, if we put a postponed message, it will remain in memory until its time comes. Along with the message in memory will be all the objects referenced by the message, explicitly and implicitly.
And with this you need to do something.

Static class solution


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



We start, turn the phone a couple of times and collect a memory dump.



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



Pay attention to the very 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 memory leaks. We need to do something else.

Solution using static class and WeakReference


Let's continue the sequential method of getting rid of the link to TextView, which we found during the study of memory dumps.



Please note that we save the link to the TextView in the 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 last one, checking it for null.

We start, we turn and we collect memory dump.



We have achieved the desired! Only one Activity in memory. Problem solved.

To use this approach, we need:


Is this method good?
If we compare the original code and the "safe" code, then a large amount of "noise" catches the eye. 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.

Well, there are better solutions.

Purging 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 problem - do not forget to call for cleaning in onDestroy methods or where you need to clean the memory.

We have another method in stock, which you may like a lot more.

WeakHandler Solution



The Badoo team wrote its Handler - WeakHandler . This is a class that behaves perfectly like a Handler, but excludes memory leaks.

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



Very similar to the original code, isn't it? Just one small detail: instead of using android.os.Handler, we used WeakHandler . Let's run, turn the phone several times and take a memory dump.



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

If you like this method, here's 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 .

WeakHandler principle of operation


The main idea is to keep a hard link to messages or Runnable as long as there is a hard link to WeakHandler . As soon as WeakHandler can be removed from memory, everything else should be removed with it.

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



Pay attention to the upper diagram: Activity refers to Handler, which posts Runnable (puts it in the message queue referenced by Thread). Everything is not bad, except for the implicit backlink from Runnable to Activity. As long as Message is in the queue, which lives while the Thread is alive, the entire graph cannot be collected by the garbage collector. Including fat Activity.

In the bottom diagram, Activity refers to WeakHandler, which holds the Handler inside. When we ask him to put Runnable, he wraps it up in WeakRunnable and posts it in a queue. Thus, the message queue refers only to WeakRunnable. WeakRunnable contains WeakReference on the original Runnable, i.e. the garbage collector can clean it up at any time. Whatever he clears ahead of time, WeakHandler holds a hard link to Runnable. But as soon as WeakHandler itself can be deleted, Runnable can also be deleted.

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

findings


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


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 is our favorite, but you need to be careful: WeakHandler should have an external link as long as you need it, otherwise the garbage collector will delete it along with all messages from the queue.

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

Article in our English-language blog: bit.ly/AndroidHandlerMemoryLeaks

Dmitry Voronkevich, Lead Developer

Source: https://habr.com/ru/post/240479/


All Articles