深入理解Handler消息机制
前言
消息机制预计共分为两篇,本篇为第一篇,基础篇,消息机制是在面试中必问的知识点,虽然现在在实际工作中,已经没怎么使用到handler,如我们可以使用rxjava来做线程切换,但其实其内部核心仍然是handler,只是做了一层高级封装,下面我们就来深入分析handler消息机制原理
handler消息机制所涉及到的类大致有以下五个
- handler
- Looper
- MessageQueue
- Message
- ThreadLocal
整个消息机制分为如下展开:
- handler是如何将消息发送到消息队列的?
- message是如何添加到messageQueue中的?
- Looper是如何轮询消息队列的?
- Looper取出消息后,是如何分发到handler的handleMessage进行处理的?
一. handler是如何将消息发送到消息队列messageQueue的?
我们最常使用的发送消息的sendMessage(),或者延迟发送sendMessageDelayed()最终都会走到enqueueMessage()方法
方法执行流程如下:
sendMessage() –> sendMessageDelayed() –> sendMessageAtTime() –> enqueueMessage() –> queue.enqueueMessage()
最终handler会调用messageQueue的enqueueMessage()方法,传入message,添加到消息队列中
sendMessage
1 | public final boolean sendMessage( Message msg){ |
sendMessageDelayed
1 | public final boolean sendMessageDelayed(long delayMillis) Message msg, { |
sendMessageAtTime
1 | public boolean sendMessageAtTime(long uptimeMillis) Message msg, { |
enqueueMessage 注意这句话:msg.target = this; this就是当前发送消息的handler,赋值给msg,这样在取出消息的时候,就会分发给handler处理消息
1 | private boolean enqueueMessage( MessageQueue queue, Message msg, |
疑问:这里的queue是哪里来的,从后面Looper的分析中,我们知道,创建Looper的时候,会创建MessageQueue,为了验证,我们看下handler的构造方法:
1 | public Handler(boolean async) Callback callback, { |
创建handler的时候,会去获取当前线程的looper,然后从looper中获取messageQueue,后面handler发消息的时候,就是发送到这个messageQueue中
二.message是如何添加到messageQueue中的?
前置知识:messageQueue的数据结构 -> 单链表
1 | boolean enqueueMessage(Message msg, long when){ |
整个messageQueue的数据结构是一个单链表,头结点是mMessages
分析1:判断message是否要作为头结点,p=null 说明消息队列中没有消息,when < p.when 说明消息要最先被执行,执行if中的代码,message就插入到头部
分析2:遍历链表的每个结点,比较when,找出插入位置(计算出prev和p)
分析3:for循环结束后,message会插入到prev和p之间
三. Looper是如何轮询消息队列的?
1.Looper是如何创建的?
主线程的Looper创建完成后,会存入主线程的threadLocal中
APP从framework层进入Java世界,就是通过执行activityThread的main方法,我们知道,启动一个线程,可以通过创建thread,然后调用start来开启线程,但是我们的主线程activityThread并不是继承自thread或者runnable,但是我们学过java,执行一个类的main方法就会启动线程,这也就是主线程的启动,下面我们来看main方法中关于looper的部分
1 | public static void main(String[] args) { |
主线程Looper的创建过程
prepareMainLooper()
1 | public static void prepareMainLooper() { |
prepare()
1 | private static void prepare(boolean quitAllowed) { |
分析1 prepare方法只能调用一次,否则这里会抛出异常
分析2:创建Looper对象,存入当前线程的threadLocal
分析3:Looper的构造方法,在这里创建了MessageQueue消息队列
ThreadLocal
ThreadLocal是一个线程存储类,存储线程私有的数据,内部是一个map结构
前面的prepare()方法中,可以看到,新创建的Looper对象,存储到了ThreadLocal中
2.Looper如何轮询消息队列的?
给出loop()的核心方法,在死循环中获取消息,并分发消息给handler处理
1 | public static void loop(){ |
分析1:queue.next()
会获取消息队列里面的一条消息,如果没有消息,就会进入nativePollOnce()阻塞等待,当一条消息插入到消息队列后,就会调用nativeWake() 唤醒,唤醒的地方就是nativePollOnce(),这也解释了一个面试题:为什么Looper的loop循环,不会卡主线程,因为主线程大部分时间都是处于阻塞等待(睡眠),这个时候,CPU会释放相关资源
分析2:msg.target.dispatchMessage(msg);
前面已经分析了,这里的target就是发送此消息的handler,下面来看dispatchMessage
1 | public void dispatchMessage( Message msg){ |
四 Message缓存池
1.Message消息池
一般我们都会从消息池中复用一条消息,而不是new
1 | Message msg = Message.obtain() |
接下来,我们来看obtain()方法
1 | public static Message obtain() { |
解析1:sPool就是Message,相当于一个指针,其实消息复用池的数据结构就是一个单链表,sPool就是指针,将当前sPool的message对象返回,同时将sPool指向next(下一个消息)
五.常见面试题:
1.一个线程可以有几个handler?
答案:一个线程中可以有多个handler,我们可以自己创建handler来发送消息,同时我们知道ActivityThread内部有个mh,也就是handler,主要用于Android处理系统类的消息,也就是说一个线程中可以有多个handler,但是只有一个Looper