“深入交流“系列:Handler消息机制的原理
Handler消息机制的原理解析
前言
Handler
消息机制作为Android
系统运行的基础,是开发人员必须要了解的,网上的文章有很多,这里我也简单的叙述一下自己的理解。Android
中的异步消息处理机制主要由4
个部分组成:Handler
、Message
、MessageQueue
、Looper
。我们首先来简单的介绍一下这4
个组成部分。
Handler
Handler
主要的作用就是用来发送消息和处理消息。Android
中规定访问UI
只能在主线程中进行,如果在子线程中访问UI
,就会抛出异常,而Handler
就可以将一个任务切换到某个指定的线程中去执行,完美的解决了这个问题。
Message
Message
是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。
MessageQueue
MessageQueue
是消息队列,它由单链表构成,内部存储了一组消息并且以队列的方式对外提供插入和删除的工作。MessageQueue
作为消息的存储单元,它并不能去处理消息,消息的处理需要依靠Looper
进行。消息队列中的消息一般分为三种类型:BarrierMessage
(同步屏障)、AsyncMessage
(异步消息)、Message
(同步消息)。同步屏障消息并不会被执行,只是一个标记。
Looper
Looper
用来获取消息队列中的消息,使得在必要的时间让消息得到执行。Looper
的loop()
方法中会开启一个无限循环,用于不断的从MessageQueue
中查找消息。如果有消息的话就会取出消息传递给Handler
的dispatchMessage()
方法中进行处理。如果没有消息就会一直等待。
private Looper(boolean quitAllowed) {
// 创建Looper的时候会创建一个消息队列
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
一个线程中最多只有一个Looper
,一个Looper
中只有一个消息队列,一个MessageQueue
中可以有多个Message
,一个MessageQueue
可以对应多个Handler
。
Handler、MessageQueue、Looper简单汇总
这里借用Carson
大佬的一张图来对他们做一个简单的总结:原文
Hander流程简述
在主线程中创建一个Handler
对象,并重写handleMessage()
方法,然后当子线程中需要进行UI
操作时,就创建一个Message
对象,并通过Handler
将这条消息发送出去。之后这条消息会被添加到MessageQueue
的队列中等待被处理,而Looper
中的loop
方法会开启一个死循环,尝试从MessageQueue
中调用next()
方法不断的取出需要处理的消息。最后通过dispatchMessage
分发给Handler
进行处理。这里需要注意一下,Handler
的分发也是有优先级的。
- 1、
Message
的回调方法:message.callback.run()
优先级最高。 - 2、
Handler
的回调方法:Handler.mCallback.handleMessage(msg)
。 - 3、
Handler
的默认方法:Handler.handleMessage(msg)
。
消息入队
在
Handler
中发送消息的方法有很多,但是无论我们选择了哪个方法来发送消息,它最终都会调用Handler
的enqueueMessage()
方法来给消息队列MessageQueue
发送消息。
可以看到在Handler
的enqueueMessage()
方法中将msg.target
赋值为this
,这个this
也就是我们当前发送消息的这个Handler
对象,以为最终消息的处理也会交给这个Handler
来处理。紧接着就调用了MessageQueue
中的enqueueMessage
方法。
我们主要分两部分来看:
-
1、当队头消息为空
(p == null)
或者消息不需要延迟(when == 0)
或者新消息的执行时间小于队头消息的执行时间(when < p.when)
,就会进入if
语句,将新消息插入队列的头部,无论是新插入一条消息还是被消费之后从队列当中移除,mMessage
都会指向队头。mBlocked
代表当前线程是否处于阻塞状态,如果当前线程处于阻塞状态,那么此时我们插入一条消息到队头就会唤醒这个线程来分发消息。只有当队列中消息为空或者是队列中没有可执行的消息的时候,mBlocked
才会为true
,处于阻塞状态。 -
2、进入
else
语句,会开启一个无限for
循环,这个for
循环的本质目的就是找到一个合适的位置将新消息插入进去,找到合适的位置之后就会跳出for
循环,然后调整链表中节点的指向关系,从而实现新消息插入队列的消息。
消息分发
队列中的消息之所以能够分发是因为Looper
的loop()
方法会开启一个无限的for
循环,然后调用MessageQueue.next()
获取可执行的消息。这个next()
方法可能会导致当前线程进入一个阻塞的状态,那么此时这个方法不会有返回值,后续的方法不会执行,直到有可处理的消息返回才会执行。消息处理完之后还会进行回收。接下来进入MessageQueue
中的enqueueMessage()
方法。
可以看到在next()
方法中同样开启了一个无限for
循环
- 1、
natviePollOnce(ptr,nextTimeoutMills)
:前面提到队列中没有可处理的消息会进入阻塞状态,就是这个方法来完成的,我们主要来看这个方法的第二个参数nextTimeoutMills
,如果nextTimeoutMills > 0
,当前线程就会进入阻塞状态,并且会释放掉cpu
使用权,第一次循环的时候nextTimeoutMills = 0
,不会进行休阻塞,如果在后面的过程中没有找到合适的可处理的message
,nextTimeoutMills
这个值会被更新,第二次循环的时候如果不等于0
,就会进行阻塞,假如nextTimeoutMills = 1000
,那么线程就会阻塞1000ms
,1000ms
之后继续执行后续代码。如果nextTimeoutMills = -1
,那么当前线程会无限的休眠,直到有新的消息进入,唤醒这个线程。 - 2、判断队头是否为同步屏障消息,
msg.target == null
即为屏障消息,如果是屏障消息,就会进行do-while
循环,while
语句中会判断是否为同步消息,如果是同步消息就执行do
语句中的代码,如果是异步消息,就退出do-while
循环进行后续代码的执行。所以屏障消息的作用就是会从消息队列中获取消息的时候跳过同步消息来检索异步消息。 - 3、如果
msg.target != null
,就会按照时间顺序来查找分发消息,这里我们先看else
语句,也就是图中的第5
步,msg == null
的时候进入else
语句,表示队头消息为空,也就意味着队列是空的,里面没有消息,然后就会将nextPollTimeoutMills
的值置为-1
,前面我们提到nextPollTimeoutMills = -1
的时候,线程会进入无休止的阻塞,直到新消息到达才会唤醒线程。 - 4、
now < msg.when
表示找到可以处理的消息了,但是还没有达到此消息执行的时间。此时会重新计算nextPollTimeoutMills
的时间。 - 5、最终找到了需要处理的消息,需要将其从队列中移除,
preMsg
不为空,表示我们将要删除的消息是队列中间的一条消息,preMsg
为空删除的是队头消息。需要重新指向队头消息。最后返回msg
对象。
最后取出可处理的消息之后就会调用msg.target.dispatchMessage(msg)
方法来分发消息。
同步屏障
可以看到在插入消息的过程中,并没有给Message
的target
进行赋值,所以此时这条消息的target = null
,也就意味着开启了同步屏障。开启了同步屏障之后,会略过同步消息,异步消息就会优先被处理,那么什么时候会处理同步消息呢,需要调用 removeSyncBarrier
将同步屏障移除之后,才会处理同步消息,如果不移除同步屏障,同步消息就不会被处理。
转载自:https://juejin.cn/post/7138605331625541663