December 8, 2016

Avoid Memory Leaks in Android

Java is a garbage-collecting-language, the garbage collector deals with memory properly on most cases. But we may still encounter memory leaks due to some logical bugs. I'll list some common cases that cause memory leaks in Android.

Static Reference

I just take static reference to activity for example. It's a typical case.
If an activity's onDestroy() method gets called, garbage collector should be able to recycle it. But garbage collector cannot recycle an object if other GC root holds strong reference to it. I used to see code like this:

public class MyApplication extends Application {
    public static Activity sTopActivity;
}

The programmer intends to hold a static reference to the activity on top of the stack. If this reference doesn't get reassigned on every activity's onCreate() method, it may cause activity leaks. Extending activity's lifecycle is dangerous, if you find you have to it, reconsider your design.

Handler

IdleHandler
/**
 * Callback interface for discovering when a thread is going to block
 * waiting for more messages.
 */
public static interface IdleHandler {
	/**
	 * Called when the message queue has run out of messages and will now
	 * wait for more.  Return true to keep your idle handler active, false
	 * to have it removed.  This may be called if there are still messages
	 * pending in the queue, but they are all scheduled to be dispatched
	 * after the current time.
	 */
	boolean queueIdle();
}

We may call this method to add IdleHandler to a message queue:

/**
 * Add a new {@link IdleHandler} to this message queue.  This may be
 * removed automatically for you by returning false from
 * {@link IdleHandler#queueIdle IdleHandler.queueIdle()} when it is
 * invoked, or explicitly removing it with {@link #removeIdleHandler}.
 * 
 * <p>This method is safe to call from any thread.
 * 
 * @param handler The IdleHandler to be added.
 */
public void addIdleHandler(IdleHandler handler) {
	if (handler == null) {
		throw new NullPointerException("Can't add a null IdleHandler");
	}
	synchronized (this) {
		mIdleHandlers.add(handler);
	}
}

If we didn't remove idle handler manually, Looper.loop() will keep running, mIdleHandlers holds reference to idle handler we added before, the idle handler may hold reference to the activity if it's a non-static inner class, thus cause activity leaks.
So make sure to remove idle handler on activity's onDestroy() callback.

Delayed Message

If we post a message or runnable with sendMessageDelayed(Message msg, long delayMillis) or postDelayed(Runnable r, long delayMillis), there's a chance that these messages haven't been handled yet when the activity finished. Messages in message queue hold reference to handlers, handlers may referencing activity if it's a non-static inner class.
Remove all messages in onDestroy() or make the handler class static can both avoid activity leaks.

Thread and AsyncTask

Life cycle of a thread is unpredictable, it's very likely that threads created in activity haven't finished execution after activity's onDestroy() get called. Thread and AsyncTask holds reference to activity if it's a non-static inner class. Activity cannot be recycled before worker thread finish execution.
To solve this problem, we made these inner class static. If static inner class needs access to activity's member field, create a WeakReference inside the static inner class, it won't prevent garbage collector from recycling the activity.

WebView

Before Android Honeycomb, there's a disgusting bug with WebView, if the context passed to webview is activity's context, this activity can't be recycled, if it's application's context, this webview can't show dialog and start other activity.
To work around this problem, we usually start webview in a separate process. Once the webview finish its work, we kill the process to avoid memory leak. If the webview process need to exchange data with main process, we need to implement some AIDL interface.