1. 线程的消息队列是怎么创建的
看几个问题
- 可以在子线程创建 handler么
- 可以,先looper.prepare
- 主线程的Looper和子线程的Looper有什么区别?
- 子线程的Looper可以退出,主线程的Looper不能退出
- 主线程的looper在启动时就创建好了
- Handler、Looper和MessageQueue有什么关系?
- java层looper包含MessageQueue,一对一关系
- 与handler是一对多的关系,可以有多个handler
- native层的MessageQueue包含一个looper
- MessageQueue是怎么创建的?
- java层,Looper构造函数中new出来的,Java层创建的时候调用nativeInit函数
- native层,会创建一个looper,looper中创建一个eventFd,并且添加一个可读事件到epoll里(见图91)
子线程创建handler
- 子线程创建handler的前提是要先调用looper.preapare()函数
- 否则会抛出异常,如图89
handler源码分析
- looper需要从threadLoacl中get出来
- 如果looper==null,就会抛出上面的异常
- 如何得到looper呢,无论子线程还是主线程都是通过prepare函数调用获得的,quitAllowed标记looper是否可以退出
- 子线程的looper是可以退出的
- 主线程的looper是不能退出的
- 图90
- 主线程的looper会保存在一个静态变量中
- 下面看looper的构造函数
-
图91
-
looper构造函数中构造了一个messageQueue
-
messageQueue保存了quitAllowed变量,然后在native层做了一些初始化操作
-
Handler、Looper和MessageQueue的关系
- 一个线程包含一个looper,looper中包含了一个messageQueue,一个looper可能对应多个handler
- handler的消息都发到了线程looper的MessageQueue中
- MessageQueue分发消息会分别分发到三个hander,分发逻辑是根据message的target值
消息队列的整体架构
- 分两层,java层和native层,核心实现在native层
- java层,一个线程线程对应一个looper,对应一个MessageQueue,对应一或多个Handler
- Looper和MessageQueue在native层都有对应的native对象
- native层的MessageQueue有一个mLooper,指向native层的Looper
2. 说说android线程间消息传递机制
考察点
- 消息循环过程是怎样的?
- 即looper.loop原理
- 消息是怎么发送的?
- Handler.sendMessage
- 消息是怎么处理的?
- handler.dispatchMessage
消息循环
- 先拿到当前线程的looper和messageQueue
- for循环遍历,不断调用next(),拿到下一条要处理的消息
- 通过msg.target(实质上是一个handler)分发事件到对应的handler,回收消息
- 看一下消息的分发
- 如图95
- 如果消息自带callback,优先用callback处理
- 否则如果全局callback不为空,用全局callback处理
- 最后才会走到handleMessage
- 如图95
- 再看一下如何从消息队列取下一条消息
- 如图96
- 通过一个for循环,nativePollOnce是一个native函数,会一直阻塞,阻塞在这是为了监听事件,如果有别的线程往当前的消息队列发消息,就会唤醒这个阻塞并返回,或者到了超时时间也会返回
- 返回之后就会从消息队列里取一条消息,消息队列是一个单链表,从单链表表头取了一条消息,并且标记为markUse,也就是使用中的意思,然后将消息返回
- nativePollOnce第一次是立即返回的
- 如果没有新消息,nativePollOnce超时时间会被设为-1,等到有新消息的时候才会返回
- 如图96
如何向消息队列发消息
- 先看代码逻辑
- 如图97
- 如图97
- 在看图99
- 图99
- 图中有两个线程 A和B
- 线程A中有一个消息队列及一个eventFd,线程A有两种状态,运行状态和阻塞状态,阻塞在eventfd上,监听eventFd事件
- 当线程B往线程A的消息队列中发送消息的时候,同时也会向eventFd中写一个数,这样线程A就能收到事件了,就会被唤醒来处理这些消息
- 图99
3. handler的消息延时是怎么实现的?
先看几个问题
- 消息延时是做了什么特殊处理么?
- 消息队列messageQueue每次插入消息都会按时间顺序排列
- 是发送延时了,还是消息处理延时了?
- 处理消息延时
- 延时精度怎么样?
延时消息的处理
- 延时发送消息实质上就是按消息的时间顺序把消息插入到队列中(按时间顺序排序)
- 来看一下去下一条消息的next函数
- 如图100
- 如果没有下一条消息,就会一直阻塞,有消息就会返回
- 所谓的“有下一条消息”,指的是下一条可用的消息,比如消息队列中有一条消息,但是这个消息的触发时间还没到,那么这就不是一条可用的消息
- 如果第一条消息的处理时间还没到,就需要等待msg.when-now的时间,并赋值给nextPollTimeoutMillis即超时时间,时间一到,nativePollOnce -> epoll_wait就会自动返回,下一次检查条件满足
- 如果时间到了,就会返回消息分发
- 如图100
答题要点
- 消息队列按消息触发时间排序
- 即越早要触发的消息就越排在前面
- 设置epoll_wait的超时时间,使其在特定时间唤醒
- 先计算好要触发的时间距离当前时间多长,把这个时间当做epoll_wait的超时时间,epoll_wait超时的时间就是消息触发的时间,这个时候线程会被唤醒,检查消息队列是否有新的消息要处理
- 关于延时精度
- 不是很精确,epoll_wait的超时时间不够精确,另外一点,消息队列有些消息处理非常耗时,导致后来的消息延时处理了
4. 说说IdleHandler的原理
考察点
- 了解IdleHandler的作用以及调用方式
- 线程消息队列空闲时执行
- 了解IdleHandler有哪些使用场景
- 延迟执行
- 批量任务
- 熟悉IdleHandler的实现原理
- 在MessageQueue中添加到IdleHandler数组中
- MessageQueue.next()调用,没有新的消息分发时,会调用
- 返回true会保留IdleHandler,返回false会移除
IdleHandler的作用以及调用方式
/**
* Callback interface for discovering when a thread is going to block
* waiting for more messages.
*/
public static interface IdleHandler {
/**
* Called when the message queue has run out of messages and will now
* wait for more. Return true to keep your idle handler active, false
* to have it removed. This may be called if there are still messages
* pending in the queue, but they are all scheduled to be dispatched
* after the current time.
*/
boolean queueIdle();
}
- 注释中明确的指出当消息队列空闲时会执行IdelHandler的queueIdle()方法,该方法返回一个boolean值,如果为false则执行完毕之后移除这条消息, 如果为true则保留表示IdelHandler一直有效,等到下次空闲时会再次执行
- 看下IdleHandler的使用,如图101
- 图101
- 可以看出idelHandler的调用时机是当消息队列没有新消息可以分发的时候,就会处理idelHandler
- 图101
FrameWork中IdleHandler的使用
- 先看ActivityThread中的一个函数scheduleGcIdler
- 图102
- scheduleGcIdler向主线程添加了一个IdleHandler,这个gcIdler的doGcIfNeeded函数主动触发了一次GC,return false说明主线程在idle的时候只会触发一次gc,也就是主线程第一次idel的时候会触发gc
- 图102
- 在看一下同步等待线程idle的技巧
- 图103
- 当mIdle为true的时候,退出循环,函数返回
- 图103
适用场景
- 延迟执行
- 图104
- 当主线程繁忙的时候,有些非紧急的事情可以采取延时加载的方式,但是这个时间是多久其实很难测算,虽然可以采用hander.postDelayed(delayTime)的方式,但是IdleHandler也是一个很好的解决方式
- 图104
- 批量任务
- 批量任务有两个特点
- ①任务密集
- ②只关注最终结果
- 比如打开App,收到了很多条推送,现在要处理这些推送消息并刷新页面,如果刷新界面是一个繁重的任务,那么来一条推送刷一次界面就不太好了. 可以开一个工作线程,来一条推送就把它封装成一个消息丢到工作线程中处理,等到消息全部处理完成工作线程空闲下来之后,再去汇总结果刷新界面,这种场景比较适合IdleHandler
- 批量任务有两个特点
5. 主线程进入loop循环了为什么没有ANR?
考察点
- 了解ANR触发的原理
- 了解应用大致启动流程
- 了解线程的消息循环机制
- 了解应用和系统服务通信过程
ANR是什么
- ANR弹窗是在AMS中弹出的,AMS运行于system_server进程,所以ANR相当于是在系统进程中弹了一个Dialog
- 弹出ANR的实际调用函数是appNotResponding,如图105
- 图105
- 图105
- ANR场景
- Service Timeout: 服务20s之内没有执行
- BroadCastQueue TimeOut: 前台广播10s内没有执行完成
- ContentProvider TimeOut
- InputDispatching TimeOut: 输入事件超过5s没有执行,包括触屏按键等事件
ANR如何被触发 以Service为例
- 图106
- 当Service没有启动起来的时候,就会延迟弹出ANR
- 当Service启动之后,延迟弹出ANR的消息会被删除
消息循环机制
看下哪些线程可能会往主线程发送消息
- ①主线程给自己发消息(主线程running状态下)
- ②子线程给主线程发消息
- ③binder线程给主线程发消息
- 如图108
- 由于系统服务向应用端发起binder调用的时候,先跑在应用端的binder线程池中,应用在处理请求时,并没有在binder线程中处理这些请求,而是先封装了一个消息,把消息丢到主线程中处理,比如启动Activity,创建Service等
- 如图108
应用主线程loop循环,为什么没有ANR?
- ANR是应用没有在规定的时间内完成AMS指定的任务导致的
- AMS请求调到应用端binder线程,封装成消息再丢消息去唤醒主线程来处理
- 应用在一个for循环中不断地从消息队列中取消息、处理消息
- 所以,进入for循环不代表是死循环,它还是可以处理系统发来的请求
- ANR不是因为主线程loop循环,而是因为主线程中有耗时任务
- 主线程中有耗时任务的时候,其他请求会被delay,导致在规定的时间内没有完成
- 或者这个请求本身也很耗时
6. 听说过消息屏障么?
消息分类 消息分三种
- 图109
- normal:普通消息
- barrier:屏障消息(给其他消息添堵的),不需要分发
- async:异步消息,与普通消息相比多了一个异步标志位
界面绘制,输入事件这些事件都是比较紧急的,不能被普通消息耽误,所以专门设计了消息屏障,保证异步消息的优先执行
消息屏障的特点
- 屏障消息没有target,因其不需要分发,所以也就不需要handler
- 消息屏障也是带时间戳的,在消息队列按照时间排序,屏障消息只会影响到它后面的消息
- 消息队列可以插入多个屏障消息
- 发送屏障消息不会唤醒线程
- 插入屏障消息会返回一个token,这个token是它的序列号
- postSyncBarrier不是一个开放的api,只能通过反射创建
- 如果发一个不带target的消息是不被允许的,需要指定handler
看下消息屏障的移除
- 删除屏障需要带上token,根据token找到消息
- 删除屏障的时候需要唤醒线程,假设屏障消息恰好在队列头部,撤出屏障就需要唤醒线程
- 如果屏障消息不在队列头,就说明消息队列没有被屏障block住
- 删除屏障后就会马上处理被block住的消息,消息的时间到了就会被马上分发,没到时间线程就会继续休眠等待
屏障的处理
- 屏障会block住后面的普通消息(normal)
- 当遍历到屏障消息时,会继续遍历看看有没有异步消息
插入消息的时候有屏障会怎样
- 如果插到队列头了,如果当前线程是休眠的,就要唤醒他
- 如果没插到队列头,如果当前线程是休眠的,并且队列头是屏障,并且当前消息是最早的一条异步消息,就要唤醒线程。
看几个问题
- 消息队列是空的时候,插一个屏障,会触发idleHandler么?
- 不会,线程还在休眠
- 如果删除了屏障,消息队列空了,会触发idleHandler么?
- 不会,idleHandler已经调过了,不会重复触发
- 比如处理完了一条消息,再去取下一条消息,发现没有新的消息了,这时候就会调idleHandler,调完之后检查消息队列,如果检查还是没有新的消息,就会休眠;如果被唤醒了但是消息队列没有可执行的消息了,这时候不会重复调用idleHandler
- 所以:之前消息队列处理完最后一个消息之后,休眠之前肯定调过一次idleHandler,消息屏障删除时唤醒了线程,消息队列还是没有要处理的消息时,是不会重复调用idleHandler的
- 如果消息队列只有一个屏障消息,插一个普通消息会idleHandler么?
- 有可能:idleHandler的触发条件是消息队列为空或者第一条消息的时间还没到,所以关键是屏障的时间到了没有
- 如果消息队列只有一个屏障消息,插一个异步消息会idleHandler么?
- 有可能:关键是看屏障的时间到了没有
FrameWork中屏障的使用
- 界面绘制
- 图114
- 这里的逻辑是:绘制的时候插入一条消息屏障,把后面的消息block住,让界面绘制的异步消息优先执行
- 图114
- 输入事件分发