Related reading:

Implement EventBus: callback, observer mode and bus

Implement Android View Touch event distribution: Try writing your own Android View Touch event distribution

In Android development, the Handler mechanism, or messaging mechanism, is mainly used at the APP level for switching between threads (especially switching to the main thread) and delayed execution of logic. In fact, both of these functions can be replaced by Kotlin coroutines. However, the Handler mechanism is still the implementation of the Android system level message loop mechanism, and will not change, in-depth understanding of its principle is still important.

Now read the system source code, and simplify the core logic, their own implementation of the Handler mechanism.

Message

class Message {
    var what = 0
    var arg1 = 0
    var arg2 = 0
    var obj: Any? = null
    var data: Bundle? = null/ / 1

    var `when` = 0L/ / 2
    var next: Message? = null
    var callback: Runnable? = null
    var target: Handler? = null
}
Copy the code

First, Message is implemented. (1) blocks are some simple data structures encapsulated in Message, and (2) when points are the basis for the delay execution of a certain logic by the handler mechanism.

Handler

open class Handler(private vallooper: Looper = Looper.myLooper()!! .private val callback: ((Message) -> Unit)? = null) {
    private val queue: MessageQueue = looper.mQueue/ / 1
    fun dispatchMessage(msg: Message) {/ / 4msg.callback? .apply { run()return} callback? .apply {this(msg)
            return
        }
        handleMessage(msg)
    }
    fun sendMessage(msg: Message) {/ / 2
        sendMessageDelayed(msg, 0)}fun sendMessageDelayed(msg: Message, delayMillis: Long) {/ / 2
        sendMessageAtTime(msg, System.currentTimeMillis() + delayMillis)
    }
    private fun sendMessageAtTime(msg: Message, uptimeMillis: Long) {/ / 2
        msg.target = this/ / 3
        queue.enqueueMessage(msg, uptimeMillis)
    }
    fun postDelayed(r: Runnable, delayMillis: Long) {/ / 5
        sendMessageDelayed(Message().apply { callback = r }, delayMillis)
    }
    open fun handleMessage(msg: Message){}}Copy the code

(1) Message queue, (2) sendMessage will be called to the same place (3), set the target for Message, that is, the Handler itself, into the Message queue. This also indicates that messages in the same message queue are processed by different handlers. The place where messages are processed is dispatchMessage (4), where messages may be sent to one of three places, from the highest priority to the callback to Message itself, the callback in the Handler constructor, and the override of the handleMessage method. (5) The postDelayed method is essentially a wrapper around setting the Message itself callback.

Looper

class Looper private constructor() {/ / 2
    companion object {
        private val sThreadLocal = ThreadLocal<Looper>()
        fun myLooper(a): Looper? = sThreadLocal.get(a)/ / 4
        fun prepare(a) {
            if (sThreadLocal.get() = =null) {
                sThreadLocal.set(Looper())
            }
        }/ / 3
        fun loop(a){ myLooper()? .let { me ->/ / 6
                val queue = me.mQueue
                while (true) {/ / 5
                    valmsg = queue.next() ? :returnmsg.target? .dispatchMessage(msg) } } } }val mQueue = MessageQueue()/ / 1
}
Copy the code

Next, we implement Looper. (1) We can see that the property has only one directly initialized mQueue, which is the source of the message queue in Handler (1) from the previous section. Set the constructor to private (2), and use the prepare (3) static method to initialize the object and place it in sThreadLocal. (4) The myLooper method retrieves the unique Looper of each thread saved by the prepare.

Static method loop is a method that reflects the “loop” of message loop mechanism. (5) It obtains messages in queue through an infinite loop and distributes messages by calling Handler’s dispatchMessage in the previous section. The next method on queue is a bit like the blocking method take on a blocking queue, which we implement in the next section. In the source code, unless the message loop is terminated, next does not normally return NULL, causing the while loop to exit. We keep this notation here, but do not provide an implementation to terminate the message loop. The next method returns only when the message is actually available. At the same time, repeated calls to the loop method in the source code will throw an exception, here we carry out null-detection protection (6).

MessageQueue

class MessageQueue {
    var mMessages: Message? = null/ / 1
    
    @Synchronized/ / 8
    fun next(a): Message? {
        var restTime = 0L/ / 5
        while (mMessages == null|| (mMessages!! . `when` - System.currentTimeMillis() ).apply { restTime = this } > 0) {
            nativePollOnce(restTime)
        }
        val msg = mMessages!!/ / 4
        mMessages = msg.next
        return msg
    }
    
    @Synchronized/ / 8
    fun enqueueMessage(msg: Message, `when` :Long) {
        msg.`when` = `when`
        if (mMessages == null| | `when` < mMessages!! . `when`) {
            msg.next = mMessages
            mMessages = msg/ / 2
        } else {
            var cur = mMessages!!
            var next = cur.next
            while(next ! =null && `when` >= next.`when`) {
                cur = next
                next = next.next
            }
            cur.next = msg
            msg.next = next
        }/ / 3
        //(this as Object).notifyAll() //7
    }
    private fun nativePollOnce(time: Long) {/ / 6
        //try {
        // (this as Object).wait()
        //} catch (e: Exception) { }}}Copy the code

Finally, MessageQueue, although called queue, is implemented as a linked list. (1) The header node is mMessages, which is linked by next defined in the first section Message. In MessageQueue, only enqueueMessage and next operations are implemented.

EnqueueMessage is called by sendMessageAtTime in Handler. Instead of joining the end of a queue like a normal blocking queue, enqueueMessage is assigned to the end of a queue according to the time point when passed in the method. The queue is sorted according to the value of when. (2) When the queue is empty or when value is smaller than the when value of the header node, the header node needs to be updated; (3) otherwise, the linked list is traversed to find the appropriate position to join the queue and the order of when value of each node is maintained from small to large.

In general, the next method only needs the exit head node at (4). However, the queue is not allowed until the when point arrives, so the remaining time needs to be calculated (5). In this case, the thread can be sleep or an object can be wait according to the general blocking queue implementation method. However, with Android messaging, you can’t block threads. For example, ActivityThread’s main method opens the main thread message loop, but you still need to keep the main thread idle when there are no messages to handle UI interactions with the user, rather than owning the main thread.

Therefore, the native method nativePollOnce is called here. We do not pay attention to the implementation of this method, but only know that this method will release the CPU and wait for the message execution time (i.e., the queue in the while condition is not empty or when the header message arrives). We use wait/notify to simulate this mechanism by opening the comments at (6) and (7). There is a disadvantage, however, because the thread is blocked, and we cannot wait for messages from a particular thread. In addition, we set the queue in and out method to synchronous (8) to keep the message queue thread safe. However, in the source code, the actual synchronization block of the Next method does not contain the native method nativePollOnce. Here we simplify the implementation and use the synchronous method directly.

In about 100 lines of code, the entire messaging mechanism is implemented.

use

Let’s verify our implementation:

fun main(a) {
    Looper.prepare()
    start()
    Looper.loop()/ / 1
}

fun start(a) {
    val handler = object : Handler() {
        override fun handleMessage(msg: Message) {
            println(msg.obj)
        }
    }
    handler.sendMessage(Message().apply { obj = "MSG1" })
    thread {
        handler.sendMessageDelayed(Message().apply { obj = "MSG2" }, 2000)
    }
    handler.sendMessageDelayed(Message().apply { obj = "MSG3" }, 1000)}Copy the code

Start Looper (1) in the main method and call the start method. In the source code, you can send the first message from the start position or start the first component. Here we send the message in a different thread. The results are as follows:

MSG1
MSG3
MSG2
Copy the code

The 3 messages are output 1 second apart, and the program does not terminate. The code after looper.loop () would normally never execute.