Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

This article will also participate in the “Digitalstar Project” to win a creative gift package and challenge the creative incentive money!

Handler can leak memory when used improperly.

The same answers on the Internet seem to miss the point, but this article will help you understand the details again!

What is Handler used incorrectly?

What is Handler used incorrectly?

Generally, it has the following characteristics:

  1. Handler USESAnonymous inner classorThe inner classExtension that holds external classes by defaultActivityReferences:
// Anonymous inner class
override fun onCreate(savedInstanceState: Bundle?).{...val innerHandler: Handler = object : Handler(Looper.getMainLooper()) {
        override fun handleMessage(msg: Message) {
            Log.d(
                "MainActivity"."Anonymous inner handler message occurred & what:${msg.what}")}}}Copy the code
/ / inner classes
override fun onCreate(savedInstanceState: Bundle?).{...val innerHandler: Handler = MyHandler(Looper.getMainLooper())
}

inner class MyHandler(looper: Looper): Handler(looper) {
    override fun handleMessage(msg: Message) {
        Log.d(
            "MainActivity"."Inner handler message occurred & what:\${msg.what}")}}Copy the code
  1. The Handler is still reachable when the Activity exits.
    • Exit with Thread still in process, which refers to Handler
    • When the Thread ends, the Message is still in the queue for processing or in process, holding the Handler indirectly
override fun onCreate(savedInstanceState: Bundle?).{...val elseThread: Thread = object : Thread() {
        override fun run(a) {
            Log.d(
                "MainActivity"."Thread run"
            )
            
            sleep(2000L)
            innerHandler.sendEmptyMessage(1)
        }
    }.apply { start() }
}
Copy the code

Why the memory leak?

If the Activity enters the background during the execution of the Thread above, it triggers destroy due to insufficient memory. When a virtual machine marks a GC object, one of two situations occurs:

  • Thread is not finished and is active

    An active Thread is the GC Root object that holds an instance of Handler, which in turn holds an instance of the external Activity class by default.

  • The Thread ended, but the Message was not finished

    The Message sent by the Thread may still be waiting in the queue, or it may be in the middle of a handleMessage() callback. Looper now holds the Message via MessagQueue, Handler is held by Message as a target property, Handler holds the Activity, and Looper holds the Activity indirectly.

    You may not have noticed that the Main Looper of the Main thread is different from the Looper of other threads.

    To make it easy for any thread to get an instance of the main thread, Looper defines it as a static property, sMainLooper.

    public final class Looper {
        private static Looper sMainLooper;  // guarded by Looper.class.public static void prepareMainLooper(a) {
            prepare(false);
            synchronized(Looper.class) { sMainLooper = myLooper(); }}}Copy the code

    Static properties are also GC Root objects that cause the Activity to remain reachable through the above application chain.

In both cases, the Activity instance will not be marked properly until the Thread ends and the Message is processed. The Activity instance will not be recycled until then.

Can loopers from other threads cause memory leaks?

To make it easier for each thread to get its own Looper instance, Looper caches each Looper instance using the static sThreadLocal attribute.

public final class Looper {
    static final ThreadLocal<Looper> sThreadLocal = newThreadLocal<Looper>(); .public static @Nullable Looper myLooper(a) {
        returnsThreadLocal.get(); }}Copy the code

SThreadLocal is also a GC Root object, so messages can’t be collected indirectly.

The answer is no, because the Map inside ThreadLocal uses weak references to hold Looper objects, which does not make Looper and reference chain instances unrecyclable.

public class ThreadLocal<T> {...static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<? >>{
            /** The value associated with this ThreadLocal. */Object value; Entry(ThreadLocal<? > k, Object v) {super(k); value = v; }}}}Copy the code

Does the inner class Thread also cause an Activity to fail to reclaim?

To focus on memory leaks caused by handlers, we do not address the chain of references directly generated by threads.

Thread in the above code example also takes the form of an anonymous inner class, which of course also holds an Activity instance. In this case, the “Thread” will directly occupy the Acitvity instance. This is also a reference chain that will leak the Activity memory.

How to use Handler correctly?

If the condition that the Thread has ended and the Message has been processed by GC is not met, the Activity life cycle will be erroneously extended, causing a memory leak!

So how do you avoid that? In view of the above characteristics, in fact, there should be an answer.

  1. Define Handler as a static inner class
private class MainHandler(looper: Looper? , referencedObject: MainActivity?) : WeakReferenceHandler<MainActivity? >(looper, referencedObject) {override fun handleMessage(msg: Message) {
        val activity: MainActivity? = referencedObject
        if(activity ! =null) {
            // ...}}}Copy the code

We also need to weakly reference instances of external classes:

open class WeakReferenceHandler<T>(looper: Looper? , referencedObject: T) : Handler(looper!!) {private val mReference: WeakReference<T> = WeakReference(referencedObject)

    protected val referencedObject: T?
        protected get() = mReference.get()}Copy the code
  1. Correct the life cycle when onDestroy

    • When the Activity is destroyed, stop the Thread if the asynchronous task has not finished yet:

      override fun onDestroy(a) {
          super.onDestroy()
          thread.interrupt()
      }
      Copy the code
    • A Message resets its relationship with the Handler when it executes recycle() :

      override fun onDestroy(a) {
          super.onDestroy()
          thread.interrupt()
          handler.removeCallbacksAndMessages(null)}Copy the code

Do non-inner class handlers leak memory?

Anonymous inner classes or inner classes are a feature of memory leaks caused by handlers. Is there a leak if the Handler is not written as an inner class?

Like this:

override fun onCreate(...). {
    Handler(Looper.getMainLooper()).apply {
        object : Thread() {
            override fun run(a) {
                sleep(2000L)
                post { 
                    // Update ui
                }
            }
        }.apply { start() }
    }
}
Copy the code

Memory leaks are still possible.

While Handler is not an inner class, post’s Runnable is an inner class that also holds an instance of an Activity. In addition, Runnable posts to handlers are ultimately held by Message as callback properties.

Based on these two behaviors, even though Handler is not an inner class, because Runnable is an inner class, there is also a risk that the Activity will be improperly held by Thread or Main Looper.

conclusion

To review some of the main points of this article:

  • The inner class or inner class that holds an instance of the Activity should have the same life cycle as the Activity
  • If the Activity should have been destroyed, but the asynchronous task is still active or messages sent via Handler have not been processed, the life of the inner class instance will be erroneously prolonged
  • The Activity instance that should be recycled is occupied by another Thread or Main Looper
  • Remember to write the Activity as a static inner class with weak references, and to terminate the Thread or empty the Message when the Activity is destroyed