Android Small Talks: MemoryLeaks in Android

Android Small Talks: MemoryLeaks in Android

A memory leak occurs when you do not release memory allocated before, and you no longer need objects for which this memory has been previously reserved. This involves losing control over a certain area of memory until the end of the process life cycle. In some languages, we have to personally deal with releasing memory. In C we need to use the free instruction, in C++ the delete statement. And what about Java?

Memory leaks from Android apps

Java Virtual Machine helps programmers to stop thinking constantly about memory and so lulls their vigilance. It happens so because in the background operates a memory manager called Garbage Collector. It counts existing references to each object, and if it detects that no part of a program refers to one of them, it releases its place. However, a leak is still very likely to occur in this case. It’s enough to forget about one existing reference and Garbage Collector is helpless.

Memory leak in Android diagram
image source https://youtu.be/BkbHeFHn8JY?t=43s

Results

As Benjamin Franklin said, a small leak can cause the sinking of a great ship. Memory allocated for a program is limited. Dynamically allocated data accumulates on a heap and its overflow may occur.

Leak of one object is always associated with leak of a lot of objects to which it is related. Several small leaks can lead to a slowdown of a program and increase the risk of a memory error, which can completely suspend our program.

You should also remember that Android runs on mobile devices on which very often only little memory is available. We should also assume that RAM memory is not only for us but also for other programs running in the background and the system itself.

Common errors and threats

On Android, very costly, and yet the easiest to overlook is the loss of control over a view. Sometimes it has a bitmap within it and it takes a lot of memory. Even more costly may be loss of control over all the Activity as it is linked to many internal views.

Perhaps also bitmaps which are held by these views, or even whole collections of other data, which in turn may refer to yet other objects.

It’s easy to create a chain that will drag our program down. We’re wasting a whole tree of related data stored on a heap. Moreover, it works both ways, which means that if we lose control over the Fragment, not only will its internal objects leak, but also the Activity in which it is located and all its fields. This way we will reach the very root, in this case – Context.

Dependencies lifecycle

You may disbelieve it, but it’s easy to forget some of the dependencies or even to not notice them.

We must remember that in Android the Activities and Fragments have their own life cycles.

We should be particularly careful when creating a new thread inside the Activity, e.g. in order to update its status after it finishes its operations. If we won’t take care of closing the thread or ignoring its results if the activity was killed, we will get a NullPointerException.

We are working in an application that is alive, so a user may at any time close a given screen, or at least rotate it.

For example, if we are attached to the onCreate event, and start a thread there, its work starts again without interrupting the previous operation. If inside there is any hard link to the Activity, it won’t be possible to destroy it normally and it will be stored in memory, causing a leak and checking if the object didn’t become a null sometime in the meantime won’t save us from such a leak.

Moreover, we don’t even need to have any reference to any field of Activity in our thread. If it is declared in the way shown below, as an inner class of Activity, you won’t be able to remove any object of this Activity while the thread is being executed.

This is because we have created an implicit reference.

public class MyActivity extends Activity{
		//…
		private class SampleTask extends AsyncTask<Void, Void, Void> {
			//do work
		}
	}

 

The same problem occurs when you use an anonymous class of a new thread:

new AsyncTask<Void, Void, Void>(){
        	@Override
        	protected Void doInBackground(Void... params) {
            	//do work
        	}
    	}.execute();

or any operation called asynchronously from the outside:

private MyCallback callback = new MyCallback(){
		public void onSuccess(){ … }	
	}

To deal with this problem, we need to get rid of this hidden reference. The simplest solution is to use an internal static class that doesn’t bind in any way created object with an external class, in this case – Activity.

public class MyActivity extends Activity{
		//…
		private static class SampleTask extends AsyncTask<void, void="">{
    			@Override
    			protected Void doInBackground(Void... params) {
        			//do work
    			}
		};
	}

Very often after the execution of an operation performed by a thread we need to update the status of our views and save results. What to do in such a case? After all, you need references to various fields of an external class. The first thing we should do is to check whether the object assigned to the field still exists, as it might have become null at any moment.

But how to avoid a memory leak if a thread holding a reference to a field of Activity means that it cannot be deleted by Garbage Collector, even though it was closed and its life cycle ended?

WeakReference comes to help us. It differs from the usual reference, created by the sign of the assignment ‘=’, in this, that it is not counted by Garbage Collector, which means that no hard reference points to a particular object, it can be automatically deleted. Even if some weak references are still pointing to it.

public class MyActivity extends Activity {
	private TextView label;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
    	super.onCreate(savedInstanceState);
    	// (...)
    	new SampleTask(this).execute();
	}

	public void setLabel(String text){
    		label.setText(text);
	}
    
	private static class SampleTask extends AsyncTask<Void, Void, Void>{
    	private WeakReference<MyActivity> mRef;

    	public SampleTask(MyActivity activity) {
        		mRef = new WeakReference<myactivity>(activity);
    	}

    	@Override
    	protected Void doInBackground(Void... params) {
        	//do work
        	return null;
    	}

    	@Override
		protected void onPostExecute(Void aVoid) {
        		MyActivity myActivity = mRef.get();
        		if (myActivity != null){
            			myActivity.setLabel("Ready");
        		}
    		}
         };
}

 

Just like an internal static class can be a salvation, another possibility of a leak may occur if we store data in static variables and don’t remove them.

Please, remember that data assigned to the static field of a class will be remembered through the whole life cycle of the application process.

First of all, think about whether you really need such a variable and whether it isn’t just a shortcut. If you absolutely need such a solution, it is definitely a good idea to reset this field by assigning null at the right moment.

If the static field is of a primitive type, it will take at most a few bytes of memory, so this problem can be omitted. Note, however, that it is no longer possible to free this memory once the process is running.

The most important rules that will help you avoid a memory leak

  • create an internal static class in order to avoid implicit reference to an external class,
  • use WeakReference to connect to an object the state of which we are going to change asynchronously,
  • avoid storing objects in static variables,
  • unregister listeners always in pair with their registration, For example, if you register BroadcastReceiver in a method onResume(), you should unregister it in onPause(),
  • close open database immediately after performing either one or a series of transactions.

Detecting a memory error

We don’t have to wait until we notice an out-of-memory error. We should secure against that eventuality beforehand. A mistake can always happen. It is important to notice it at the right time.

Tool available with Android Studio and libraries written by preventive programmers will help us in that task.

Used memory analysis

Opening the Memory tab in Android Studio, we see a dynamic, up-to-date graph of memory used by our application. If its amount is increasing when it shouldn’t be, take a closer look at your code.

An example of proper safe implementation:

View from Android Monitor

However, for more detailed analysis you should use Android Device Monitor, which can be opened in a separate window by the sequence Tools -> Android -> Android Device Monitor.

View from Android Device Monitor

After opening:

  1. Select plugged device.
  2. Select the process running in it.
  3. Switch on the button of updating a heap.
  4. Go to the Heap tab.
  5. Click Cause GC to force a call to Garbage Collector.

From now on your heap will be tracked. We will also protect ourselves from falsely assuming there is a leak, as on the previous memory chart we may easily mistake temporary inactivity of GC, which is carried out at different times, with a leak.

In this case, we can control it. For example, if we have a leak of some Activity, we can learn about it in the following way:

  1. Open the screen you suspect.
  2. Click Cause GC and remember how much memory is used.
  3. Close the screen several times and quickly return or rotate the screen.
  4. Click Cause GC and compare the amount of used memory with the number you remembered a moment ago.

If used memory has increased and GC doesn’t help, it means that, unfortunately, you have a leak.

Leak Canary Library

Sometimes, growth may not be very noticeable, or you may simply not really know where to look for it. You can then use Leak Canary library. It was created in order to facilitate keeping track of selected objects and to inform that the memory allocated to it, was not released by GC nor the programmer. Its installation and use are very simple and it is presented on Github. You should use it only when you are in the debug mode of your application, as surely an end-user wouldn’t expect any notifications from it.

When a leak is detected a yellow logo with the message appears, and in the background begins the process of saving a file with .hprof extension. Therefore the application must have permission to write to external memory. After a few seconds, you will receive a notification in the notification bar that will take you to the full-screen graph of the tree of dependencies of leaked objects. You can easily conclude from it what exactly happened, and think about a solution.

I must add here that I encountered a problem with using the library on Android 6.0. It couldn’t generate a file with the result of its own analysis. It only showed a message about the leak and hung applications. This error is known to the creators, and I think that it will soon be fixed (tested version 1.3.1 and 1.4beta1). On Android 5.1 and in lower versions everything works right.

Remember about memory usage

To sum up, it’s better to prevent than to fix problems. Every now and then you should examine your code in this regard and see how much memory is used and if it is being properly released. When writing an application you should understand how you share memory and how to use it so that a sufficient amount of it is available at critical moments.

 

Project estimation

Let us know what product you want to build and how we can help you.

Why choose us?

Logo Mobile Trends Awards

Mobile Trends Awards 2021

Winning app in
EVERYDAY LIFE

Nagroda Legalnych Bukmacherów

Legal Bookmakers Award 2019

Best Mobile App

Mobile Trends Awards logo

Mobile Trends Awards 2023

Winning app in MCOMMERCE DEVELOPMENT

23

client reviews

Clutch logo