相关阅读:
实现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()后的代码正常情况下将永远不会执行。