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.

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);
    }
}

First solution for the above memory leak by calling the method removeCallbacksAndMessages on the handler in the onDestroy method of the activity.

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);
    }
}

Second solution for the memory leak from the handler by using static inner class and WeakReference.

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");
            }
        }
    }
}

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.

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());
        }

    }
}

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.

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());
        }

    }
}

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.

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();
    }
}

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.


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();
    }
}

The solution to fix the memory leak is as simple as setting the listener to null by adding this line in the onDestroy method.

Utility.getInstance().setListener(null);

Complete example in Github

Search within Codexpedia

Custom Search

Search the entire web

Custom Search