Eight Ways Your Android App Can STOP Leaking Memory

In part one of this blog post “Eight Ways Your Android App Can Leak Memory”, we went over eight different ways your code can cause your Android application to leak memory. Specifically, all eight leaks were leaking an Activity instance, which is particularly dangerous because activities have a very large footprint in memory. Despite all the different ways to leak an Activity that we presented, every problem really boils down to one fundamental issue: preserving a reference to the Activity after its defined lifecycle has completed. Thankfully, once a leak has been tracked down and identified, the solution is usually rather simple.

1. Static Activities

This leak

    private static MainActivity activity;

    void setStaticActivity() {
        activity = this;
    }

was constructed to reveal the consequences of holding a reference to your activity in a static class variable that will outlive any particular instance of the activity.The activity’s class object is app-global and will preserve the activity in memory for the indefinite future. There are legitimate reasons a developer may choose to do this and therefore we need to come up with a solution that does not prevent the activity from being garbage collected once it is ready to be destroyed. Android provides a special set of objects https://developer.android.com/reference/java/lang/ref/package-summary.html#classes that allow the developer to control the “strength” of a reference. The activity is being leaked because it continues to be strongly referenced even after the intention was to destroy it and release it from memory. The garbage collector cannot clean up the activity’s memory as long as this reference exists. Therefore we can solve the leak by using a WeakReference https://developer.android.com/reference/java/lang/ref/WeakReference.html. Weak references do not prevent the object’s memory from being reclaimed and therefore if only weak references to the object remain, it will become eligible for garbage collection.

    private static WeakReference<MainActivity> activityReference;

    void setStaticActivity() {
        activityReference = new WeakReference<MainActivity>(this);
    }

2. Static Views

Statically maintaining a reference to a View

    private static View view;

    void setStaticView() {
        view = findViewById(R.id.sv_button);
    }

from an Activity is just as problematic as a direct reference to the Activity itself because Views contain a reference to the Activity they’re housed in. Therefore a WeakReference would be just as effective in solving this leak. However, we can also manually clear the reference when it is clear that the Activity object is at the end of its lifecycle and would appreciate a meeting with the garbage collector. To do this, we simply override the Activity’s onDestroy() method, which is guaranteed to be called at end-of-life, and set the reference to null.

    private static View view;

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (view != null) {
            unsetStaticView();
        }
    }

    void unsetStaticView() {
        view = null;
    }

3. Inner Classes

The leak

    private static Object inner;

    void createInnerClass() {
        class InnerClass {
        }
        inner = new InnerClass();
    }

we created is very similar to the two above. Developers are often cautioned to avoid non-static nested classes, known as inner classes https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html, because they hold an implied reference to the outer class and therefore it is very easy to inadvertently leak your Activity. However there are benefits to using an inner class such as being able to access even the private members of the outer class and as long as we are aware of the lifetime of our reference, we can prevent a leak. Once again, we naively created a static reference, this time to an instance of our inner class. To solve the leak, we can just as easily avoid declaring the reference as static and continue with business as usual.

    private Object inner;

    void createInnerClass() {
        class InnerClass {
        }
        inner = new InnerClass();
    }

4-7. Anonymous Classes

So far the root cause of every memory leak we’ve seen is an app-global static reference that either directly or indirectly through a chain of other references holds onto the Activity object and prevents it from being garbage collected. The leaks we created using AsyncTask:

    void startAsyncTask() {
        new AsyncTask<Void, Void, Void>() {
            @Override protected Void doInBackground(Void... params) {
                while(true);
            }
        }.execute();
    }

Handler:

    void createHandler() {
        new Handler() {
            @Override public void handleMessage(Message message) {
                super.handleMessage(message);
            }
        }.postDelayed(new Runnable() {
            @Override public void run() {
                while(true);
            }
        }, Long.MAX_VALUE >> 1);
    }

Thread:

    void spawnThread() {
        new Thread() {
            @Override public void run() {
                while(true);
            }
        }.start();
    }

and TimerTask:

    void scheduleTimer() {
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                while(true);
            }
        }, Long.MAX_VALUE >> 1);
    }

are all caused by declaring an anonymous class https://docs.oracle.com/javase/tutorial/java/javaOO/anonymousclasses.html. An anonymous class is actually just a specialized inner class whose main benefit is allowing code to be written concisely. Whenever a particular task only requires a one-time subclassing of a particular class, Java provides syntax sugar that lets the subclass be declared in-place as an expression with minimal syntax. This is great for writing clean code but can lead to a new, but closely related set of memory leaks. As we saw above for inner classes, their instances are completely harmless as long as you don’t create a reference to them that outlives the lifecycle of the Activity they’re declared in. However, these particular anonymous classes are all being used to spawn background threads for the application. These java threads are app-global and maintain a reference to the object that created them, the anonymous class instance, which in turn holds a reference to the outer class because it’s a non-static inner class. The threads may run in perpetuity and therefore persist a memory chain to the Activity that keeps the garbage collector from doing its job even after the Activity’s lifecycle is complete. This time we cannot just avoid declaring the reference as static because the thread is global to the state of the app. Instead for the sake of avoiding an Activity leak, we must abandon the conciseness of an anonymous class and declare each of our subclasses as static nested classes. Once a nested class is static, it no longer maintains a reference to the outer class instances and breaks our reference chain. There is nothing that inherently differentiates these particular classes and we can apply the same technique to AsyncTask:

    private static class NimbleTask extends AsyncTask<Void, Void, Void> {
        @Override protected Void doInBackground(Void... params) {
            while(true);
        }
    }

    void startAsyncTask() {
        new NimbleTask().execute();
    }

Handler:

    private static class NimbleHandler extends Handler {
        @Override public void handleMessage(Message message) {
            super.handleMessage(message);
        }
    }

    private static class NimbleRunnable implements Runnable {
        @Override public void run() {
            while(true);
        }
    }

    void createHandler() {
        new NimbleHandler().postDelayed(new NimbleRunnable(), Long.MAX_VALUE >> 1);
    }

and TimerTask:

    private static class NimbleTimerTask extends TimerTask {
        @Override public void run() {
            while(true);
        }
    }

    void scheduleTimer() {
        new Timer().schedule(new NimbleTimerTask(), Long.MAX_VALUE >> 1);
    }

However if you insist on using an anonymous class, you always have the option of terminating the java thread that’s keeping the object alive longer than the Activity. The following is just one of many ways to accomplish this for an anonymously declared Thread. Since we want the Thread to end along with the Activity, all we have to do is design the run loop to rely on the Thread’s interrupted flag and then set the flag in the Activity’s onDestroy() method.

    private Thread thread;

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (thread != null) {
            thread.interrupt();
        }
    }

    void spawnThread() {
        thread = new Thread() {
            @Override public void run() {
                while (!isInterrupted()) {
                }
            }
        }
        thread.start();
    }

8. Sensor Manager

This example

    void registerListener() {
        SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
        Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
        sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
    }

is just one of many ways that utilizing an Android system service can leak your Activity. In order to facilitate communication between the system service and the Activity, we register the Activity as a listener, and therefore create a reference chain between the service’s sensor manager event queue and our Activity. As long as our Activity remains registered with the sensor manager, the reference will not be released and the leak will persist. Once the activity is at the end of its lifetime, there really isn’t any reason it should continue to listen to events from any sensor. To solve the leak, all we need to do is unregister the listener at the Activity’s end-of-life.

    private SensorManager sensorManager;
    private Sensor sensor;

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (sensor != null) {
            unregisterListener();
        }
    }

    void unregisterListener() {
        sensorManager.unregisterListener(this, sensor);
    }

Activity leaks are all rooted in the particular cases we’ve seen here and in those that are very similar to them. Now that we’ve suggested how to address these specific issues you can apply the same techniques to any future leaks that might spring up. Memory leaks are really easy to squash once they’ve been identified so as long as you’re checking for them often you can catch them early on and create the best possible experience for your users.

You can find the full source code for this example here and the very same application uploaded to NimbleDroid here.