Memory leak examples and solutions in Android

Memory leak in Java is when an object is no longer being used but unable to be garbaged collected because it is still being referenced some other places in the application. As a result, this unused object is occupying the memory resource even though it is not being used by the application. You application will eventually crash if too much of these unused objects piled up to the point there is not enough memory left for other active objects in your application. You will see crash with out of memory error.

In android, it is very esay to run into memory leaks without even notice it. In most cases, it is because a background thread outlives the parent class, when the parent activity or fragment is destroyed, but the background thread is not and it’s holding a reference of the parent class. So, even though the parent class is destroyed by configuration change such as screen rotation, it is not being garbage collected and still occupying the memory space. Unregistered listener and anonymous inner class are also very common to introduce memory leaks in Android.

Memory leak example and solutions when using handler and anonymous runnable

This class will have memory leak, because the anonymous Runnable is holding an implicit reference of the Activity. When the Activity is destroyed within 10 seconds after it’s created, it will have a memory leak becasuse the anonymous runnable is still holding a reference of the activity which caused the activity not being garbage collected.
[code language=”java”]
public class HandlerExample extends AppCompatActivity {

private Handler mLeakyHandler = new Handler();
private TextView myTextBox;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_samples);
myTextBox = (TextView) findViewById(R.id.tv_handler);

// Post a message and delay its execution for 10 seconds.
mLeakyHandler.postDelayed(new Runnable() {
@Override
public void run() {
myTextBox.setText("Done");
}
}, 1000 * 10);
}
}
[/code]

First solution for the above memory leak by calling the method removeCallbacksAndMessages on the handler in the onDestroy method of the activity.
[code language=”java”]
public class HandlerExample extends AppCompatActivity {

private Handler mLeakyHandler = new Handler();
private TextView myTextBox;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_samples);
myTextBox = (TextView) findViewById(R.id.tv_handler);

// Post a message and delay its execution for 10 seconds.
mLeakyHandler.postDelayed(new Runnable() {
@Override
public void run() {
myTextBox.setText("Done");
}
}, 1000 * 10);
}

@Override
protected void onDestroy() {
super.onDestroy();
//This resolves the memory leak by removing the handler references.
mLeakyHandler.removeCallbacksAndMessages(null);
}
}
[/code]

Second solution for the memory leak from the handler by using static inner class and WeakReference.
[code language=”java”]
public class HandlerExample extends AppCompatActivity {
private Handler mLeakyHandler = new Handler();
private TextView tvText;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_samples);
tvText = (TextView) findViewById(R.id.tv_handler);

// Post a message and delay its execution for 10 seconds.
mLeakyHandler.postDelayed(new MyRunnable(tvText), 1000 * 10);
}
private static class MyRunnable implements Runnable {
WeakReference<TextView> tvText;
public MyRunnable(TextView tvText) {
this.tvText = new WeakReference<TextView>(tvText);
}

@Override
public void run() {
//Save the TextView to a local variable because the weak referenced object could become empty at any time
TextView mText = tvText.get();
if (mText != null) {
mText.setText("Done");
}
}
}
}
[/code]

Memory leak example and solution when using AsyncTask

This example will have memory leaks when you rotate the device within 10 seconds after it’s created. The activity is destroyed on screen rotation, but since the AsyncTask is declared as non-static class, the background task is still holding a reference of the activity which made the activty not eligible for garbage collection, thus it becomes a memory leak.
[code language=”java”]
public class AsyncTaskLeak extends AppCompatActivity {

private TextView tvText;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.activity_samples);
tvText = (TextView) findViewById(R.id.tv_sample);

new SampleTask().execute();
}

private class SampleTask extends AsyncTask<Void, Void, Void> {

@Override
protected Void doInBackground(Void… params) {
try {
Thread.sleep(1000 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}

@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
tvText.setText("Done " + new Date().getTime());
}

}
}
[/code]

The solution to the above memory leak in AsyncTask is to declare the AsyncTask as a static inner class and use a WeakReference to get a hold of the parent activity.
[code language=”java”]
public class AsyncTaskLeak extends AppCompatActivity {

private TextView tvText;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.activity_samples);
tvText = (TextView) findViewById(R.id.tv_sample);

new SampleTask(this).execute();
}

public void updateText(String text) {
tvText.setText(text);
}

private static class SampleTask extends AsyncTask<Void, Void, Void> {
private WeakReference<AsyncTaskLeak> mRef;

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

@Override
protected Void doInBackground(Void… params) {
try {
Thread.sleep(1000 * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}

@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
AsyncTaskLeak asyncTaskLeak = mRef.get();
// Make sure the activity is not null before doing anything on it because the destroyed activity could be null.
if (asyncTaskLeak != null)
asyncTaskLeak.updateText("Done " + new Date().getTime());
}

}
}
[/code]

Memory leak example and solution when using Listener

Here is a Utility class, it’s a Singleon class with a method to do long running background task, the task will call the onUpdate method on the listener.
[code language=”java”]
public class Utility {
private static Utility instance = null;
private UpdateListener listener;

//Make it a Singleton class
private Utility(){}
public static Utility getInstance() {
if (instance == null)
instance = new Utility();
return instance;
}

public void setListener(UpdateListener listener) {
this.listener = listener;
}

//Long running background thread
public void startNewTread() {
new Thread (new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000 * 10);
if (listener != null)
listener.onUpdate();
} catch (InterruptedException e) {
Log.d("Utility", e.getMessage());
}
}
}).start();
}

//Listener interface
public interface UpdateListener {
public void onUpdate();
}
}
[/code]

Here is a sample activity class that implements the listener and not clearing the listener onDestroy(), which will cause memory leak when the acitivty is destroyed by going to other screen or rotating the screen.
[code language=”java”]

public class ListenerLeak extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

//Setting the listener
Utility.getInstance().setListener(new Utility.UpdateListener() {
@Override
public void onUpdate() {
Log.d("ListenerLeak", "Something is updated!");
}
});

//Starting a background thread
Utility.getInstance().startNewTread();
}

@Override
protected void onDestroy() {
super.onDestroy();
}
}
[/code]

The solution to fix the memory leak is as simple as setting the listener to null by adding this line in the onDestroy method.
[code language=”java”]
Utility.getInstance().setListener(null);
[/code]

Complete example in Github

Search within Codexpedia

Custom Search

Search the entire web

Custom Search