实现Android消息机制

1,225 阅读5分钟

相关阅读:

实现EventBus:回调,观察者模式与总线

实现Android View Touch事件分发:尝试自己写Android View Touch事件分发

Android 开发中,Handler机制或者称为消息机制从APP层面主要用于线程间切换(尤其是切换到主线程)以及逻辑的延时执行,实际上,上述两种功能已经可以使用Kotlin协程代替。但是,Handler机制依旧是Android系统层面的消息循环机制的实现,且不会改变,深入理解其原理依然具有重要意义。

现阅读系统源码,并精简核心逻辑,自己实现Handler机制。

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
}

首先实现Message,(1)块为Message中封装的一些简单数据结构,(2)when时间点为handler机制能实现延时执行某逻辑的基础。

Handler

open class Handler(private val looper: Looper = Looper.myLooper()!!,
                   private val callback: ((Message) -> Unit)? = null) {
    private val queue: MessageQueue = looper.mQueue//1
    fun dispatchMessage(msg: Message) {//4
        msg.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) { }
}

然后实现Handler,(1)消息机制的消息队列即此处的queue,(2)sendMessage系列方法均会调用至同一处(3),在此处为Message设置target,也就是Handler本身,并入队消息队列,这也表明同一个消息队列中的消息会被不同Handler处理。而处理消息的地方就是dispatchMessage(4),这里可能将消息分发到三处,优先级从高到底为Message本身的回调,Handler构造函数中的回调,重写的handleMessage方法。(5)而postDelayed方法实际上是对设置Message本身回调的封装。

Looper

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

接下来实现Looper,(1)可以看到属性只有一个直接初始化的mQueue,也是上节Handler(1)中消息队列的来源。构造方法设置为私有(2),通过静态方法prepare(3)初始化对象并将其放在sThreadLocal中,源码中,重复调用prepare会抛出异常,这里则进行判空保护,(4)myLooper方法获取prepare保存的每个线程中独此一份的Looper。

静态方法loop就是体现消息循环机制的“循环”二字的方法,(5)通过死循环获取queue中的消息,并调用上节Handler的dispatchMessage分发消息。queue的next方法有点像是阻塞队列的阻塞方法take,下节我们实现。在源码中,除非终止消息循环,正常情况next并不会返回null,导致退出while循环,这里我们保留这种写法,但不提供终止消息循环的实现。next方法相当于能真正获取到消息时,才会返回。同时,在源码中重复调用loop方法会抛出异常,这里我们进行判空保护(6)。

MessageQueue

class MessageQueue {
    var mMessages: Message? = null//1
    
    @Synchronized//8
    fun next(): 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) { }
    }
}

最后来实现MessageQueue,虽然称为队列,但是实现为一个链表,(1)头结点为mMessages,通过第一节Message中定义的next链起来,MessageQueue中仅实现入队enqueueMessage和出队next操作。

入队enqueueMessage通过Handler中sendMessageAtTime调用,它并不是像一般阻塞队列一样加入队尾,而是根据方法中传入的时间点when确定入队位置,整个队列按照when值从小到大排序。(2)当队列为空或when值小于头结点的when值时,则需要更新头结点,(3)否则遍历链表,寻找合适位置入队,维持各节点when值从小到大排序。

出队操作next方法一般来说,只需要(4)处,出队头结点即可。但是,这里需要在when时间点到来之前不允许出队,因此需要计算剩余时间(5),按照一般阻塞队列实现的方法,此时可以sleep线程,或者wait某一对象。但是,在Android的消息机制中,是不能阻塞线程的,就比如ActivityThread的main方法中开启了主线程消息循环,但依然需要在没有消息时保持主线程空闲,以处理与用户的UI交互,而不是独占主线程。

因此,这里调用了native方法nativePollOnce,这里我们不去关注该方法实现,只需知道该方法会释放CPU,等待消息执行时机的到来(即while条件中的队列非空或头结点消息的when时刻到来)。这里我们使用wait/notify方法来模拟这种机制,打开(6)(7)二处的注释即可实现。但这样有一个缺点,因为线程被阻塞,我们不能在某一线程wait时,再通过该线程入队消息了。此外我们将出入队方法设置为同步的(8),以保证消息队列的线程安全。但在源码中,next方法实际的同步块并不会包含本地方法nativePollOnce,这里我们简化实现,直接使用同步方法。

如此,约100行代码整个消息机制实现完毕。

使用

下面,来验证一下我们的实现:

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

fun start() {
    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)
}

在main方法中启动Looper(1),同时调用start方法,在源码中,可以在start位置去发送第一个消息或是启动第一个组件,这里我们在不同线程中发送消息。结果如下:

MSG1
MSG3
MSG2

3条消息间隔1秒输出,并且程序不会终止,Looper.loop()后的代码正常情况下将永远不会执行。