Handler message mechanism and memory leak

Keywords: Android Java Attribute

1. Message mechanism

1.1 post series

By looking at the source code, it is known that the sendMessageDelayed method is the last call made by post(Runnable r), postDelayed(Runnable r, long delayMillis):

// post
 public final boolean post(Runnable r){
    return sendMessageDelayed(getPostMessage(r), 0);
}
// postDelayed
public final boolean postDelayed(Runnable r, long delayMillis){
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}

1.2 postAtTime

The postAtTime (Runnable, long uptimeMillis) ultimately calls the sendMessageAtTime method:

// postAtTime
public final boolean postAtTime(Runnable r, Object token, long uptimeMillis){
    return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
}

There is a common method getPostMessage:

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

The sentence m.callback = r shows that getPostMessage is the callback property that assigns the incoming Runnable to the Message object.

1.3 sendEmptyMessage

sendEmptyMessage eventually points to the sendEmptyMessageDelayed function:

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
}

The sentence msg.what = what shows that sendEmptyMessageDelayed is the what attribute that assigns what to the Message.

1.4 sendMessage(msg : Message)

As for the commonly used sendMessage (msg: Message), let's not go into details, it is a parameter that directly passes in a Message type.

Taken together, each method of sending a message eventually assigns the message to a Message object (or property of the message), and all these methods ultimately invoke the enqueueMessage method in the MessageQueue, which is to join the message to the message queue

1.5 enqueueMessage method

The method is longer. Let's look at the key lines:

Message prev;
for (;;) {
    prev = p;
    p = p.next;
    if (p == null || when < p.when) {
        break;
    }
    if (needWake && p.isAsynchronous()) {
        needWake = false;
    }
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;

Joining a message to the message queue (in the form of a chain table) in an infinite loop is accomplished by sending the message out and joining the queue. The next step is to take out and process the message.

1.6 Looper fetch message

There is a dead loop in Looper (Looper.loop()) to constantly pull messages from the queue:

public static void loop() {
    final Looper me = myLooper();
    final MessageQueue queue = me.mQueue;
    for (;;) {
        Message msg = queue.next();
        ...Code omission
        msg.target.dispatchMessage(msg);
        ...Code omission
        msg.recycleUnchecked();
    }
}

queue.next() fetches one Message at a time, and then submits it to msg.targer.dispatchMessage(msg) for processing from Last article As you know, msg.targer is the Handler that sends the message, so we just need to focus on dispatchMessage(msg).

1.7 dispatchMessage(msg) processing messages

dispatchMessage(msg) in Handler class

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
  1. msg callback is not empty, call handleCallback method (message.callback.run())
  2. MCallback is not empty, call mCallback.handleMessage(msg)
  3. Finally, if all else is empty, execute Handler's own handleMessage(msg) method

The first point is the above [1.1 post series] and [1.2 postAtTime], and the third point is our most common handleMessage method.It is important to note that callback.run() calls the thread's run method directly here, which is equivalent to a normal method call and does not open a new thread.

Now on point 2, there are a number of construction methods for Handler, one in addition to the public Handler(Looper looper) and Handler() mentioned in the previous article:

public Handler(Looper looper, Callback callback) {
        this(looper, callback, false);
    }

Callback is like this:

public interface Callback {
        public boolean handleMessage(Message msg);
    }

HandleMessage needs to be overridden, isn't that handleMessage inside Handler?There is a difference between the two:

// Handler
public void handleMessage(Message msg) {}


// Callback
public boolean handleMessage(Message msg);

The handleMessage return value in Callback is of Boolean type, so go back to true and false to see the effect:

class MainActivity : AppCompatActivity() {
    var handler: Handler? = null
    var looper: Looper? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        looper = Looper.getMainLooper()
        val callback = object: Handler.Callback{
            override fun handleMessage(msg: Message?): Boolean {
                Log.e("abc","--- Callback:threadName ---" + Thread.currentThread().name
                )
                return true
            }
        }
        val thread = object : Thread() {
            override fun run() {
                super.run()
                handler = object : Handler(looper, callback) {
                    override fun handleMessage(msg: Message?) {
                        super.handleMessage(msg)
                        Log.e("abc","--- handleMessage:threadName ---" + Thread.currentThread().name
                        )
                    }
                }
            }
        }

        thread.start()

        myBtn.setOnClickListener {
            handler?.sendEmptyMessage(4)
        }
    }
}

// Log Printing
--- Callback:threadName ---main

If the return value type is changed to false:

val callback = object: Handler.Callback{
            override fun handleMessage(msg: Message?): Boolean {
                Log.e("abc", "--- Callback:threadName ---" + Thread.currentThread().name
                )
                return false
            }
        }
        
// Log Printing
--- Callback:threadName ---main
--- handleMessage:threadName ---main

Therefore, handleMessage in Callback returns true and does not continue to execute handlerMessage in Handler, whereas both handleMessages execute.In fact, these can be seen from the dispatchMessage method (returning true terminates handleMessage otherwise):

 if (mCallback != null) {
    if (mCallback.handleMessage(msg)) {
        return;
    }
}
handleMessage(msg);

summary

The above mainly talks about the sending and fetching of messages, and roughly knows the workflow of the Handler message mechanism:

  1. Handler objects pass messages to MessageQueue via post(postDelayed, postAtTime) or sendMessage (sendEmptyMessage)
  2. The MessageQueue.enqueueMessage method places a Message in the Message queue as a chain table
  3. Looper.loop() loop calls next() of MessageQueue to fetch the message and handles it to Handler's dispatchMessage method
  4. The dispatchMessage() determines whether the mCallback passed in by msg.callback and the constructor is empty, either by executing their callbacks or by executing Handler's own handlerMessage method if it is empty.

2. Handler Memory Leak Problem

2.1 Causes of Memory Leakage

There is a risk of memory leaks when written as follows:

val mHandler = object : Handler() {
        override fun handleMessage(msg: Message?) {
            super.handleMessage(msg)
        }
    }

Android Studio yellows a warning and a prompt pops up when the mouse is over the handler code block, which roughly means it is recommended that you use static mode or weak references.This is equivalent to defining an anonymous internal class, which is not static and is referenced by an external class (corresponding Activity, etc.) by default.If the thread of the handler sending the message is still executing and the current Activity is finished, the handler's anonymous internal class holds a reference to the Activity, so the Activity object cannot be recycled by the GC mechanism.That is, finish code is executed, but the Activity object is still in memory (memory leak).With more and more of these objects, there is a possibility of OOM.

2.2 Solutions

There is no concept of static classes in kotlin. For example, use java static internal classes:

static class MyHandler extends Handler {
        WeakReference<MyActivity> weakActivity;

        MyHandler(MyActivity activity) {
            weakActivity = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            MyActivity activity = weakActivity.get();
            // activity.text = "......"
        }
    }
  1. Static internal classes do not hold external class references, so they do not cause Activity objects to leak (in java, "static" equals "class". Static internal classes, if they can hold external class references, indicate that external class references are internal "class". This is not logical and write compilation fails).
  2. However, the static internal class must use a reference to an external class (such as an operational UI), in which case it can use a weak reference.The code above initializes a weak reference to an Activity in the Handler constructor so that if you need to manipulate the UI, you can use activity.text = "test".

Reference article:

  1. Comb Android's messaging mechanisms (and handler's memory leaks) again from Handler.post(Runnable r)
  2. Handler Memory Leak and Solution

Posted by hstraf on Mon, 06 Apr 2020 20:52:51 -0700