前言
MessageQueue本身没有API能操作直接让Message入列,而是通过Handler的sendMessage相关方法实现入列.这里涉及:
- 内部具体由谁负责入列,怎么入;对应入列的,有取消息,怎么取
- 无消息时阻塞,怎么阻塞,阻塞多久
- 队列怎么终止
字段预览
quit:终止消息链表
这里先挑终止来讲.
终止条件
mQuitAllowed设置为true,该字段设置时机在构造函数内.而除了我们手动创建之外,系统中Looper的构造方法也会调用MessageQueue的构造方法
这里就变得有趣起来了,那也就是说Looper创建时的传值将影响消息队列是否允许退出.
这里可以区分场景:
- 系统进程如SystemServer,ActivityThread内部初始化MessageQueue时使用了false让他们的looper不支持退出
- 常规创建的MessageQueue则根据传参决定退出队列方式
此外,对于mPtr字段的控制,使用的是native方法nativeInit跟nativeWake去操作,标记该变量为不为0.
最后来看释放的方式,即安全与否方式退出.
释放消息
removeAllMessagesLocked:粗暴释放
removeAllFutureMessagesLocked:选择性释放
- 链表中剩下延时消息,直接释放
- 非延时消息,遍历释放
next:循环取消息
- 判断当前是否标记销毁标识,返回后作用外层looper退出loop方法
- 非销毁阶段,开启进入循环(正常流程).直到找到一个可处理的消息或消息队列被销毁
具体步骤:
- 处理挂起命令,减少内存和资源占用.为后续可能引发阻塞操作(如方法
nativePollOnce,同步屏障处理,空闲处理,同步代码块)提供足够内存和资源(首次循环忽略,因为进不来) nativePollOnce等待消息到达并处理消息- 同步代码块内,取到消息并进行处理,有消息就根据区分是否延时消息来操作;没消息就让下一次循环内阻塞到取到消息为止.检测是否需要执行idleHandler.
- 处理idleHandler内容
- 重置临时变量字段
1. Binder.flushPendingCommands:处理挂起命令,防止阻塞和资源占用
注释:将当前线程中挂起的任何 Binder 命令刷新到内核驱动程序。在执行可能长时间阻塞的操作之前调用此函数很有用,可以确保已释放任何挂起的对象引用,从而防止进程持有对象的时间超过其需要的时间.
- 释放资源:确保所有挂起的Binder命令都已被处理,可以释放不再需要的对象引用,减少内存占用和资源泄漏
- 防止阻塞:确保在阻塞之前处理所有挂起的命令,可以避免Binder命令在阻塞期间积累.
- 提高响应性:及时处理挂起的命令,可以提高系统的响应性,确保在处理消息时没有未处理的命令积压
为了在进入可能的阻塞操作之前,确保系统处于一个干净的状态。这样可以避免在阻塞期间持有不必要的资源,并提高系统的整体效率和响应性
2. nativePollOnce:等待消息到达并处理消息
nativePollOnce是阻塞操作,由nextPollTimeoutMillis决定阻塞时间
阻塞方式:取决于nextPollTimeoutMillis的值
- 如果为0,不阻塞.
- 如果为正值,表示阻塞指定的毫秒数,等待一段时间以查看是否有新消息到达。如果在指定时间内没有消息到达,nativePollOnce将超时并结束阻塞.
- 如果为负值,表示无限期阻塞,直到有新消息到达时结束阻塞.
不阻塞,结束阻塞都代表着nativePollOnce会返回控制权next方法.开始执行步骤3的内容.
epoll机制:内部阻塞实现
Linux内核提供的一种高效的I/O事件通知机制,用于监控多个文件描述符.采用事件驱动的方式,只在文件描述符的状态发生变化时才会通知应用程序,避免不必要轮询.
如果看过前一篇《应用进程创建二三事》,里面提到ZygoteServer的runSelectLoop里循环,也是采用了这样的处理.去告知是否阻塞,阻塞多久,超时怎么做.
3. synchronize代码块:筛选消息,空闲检查
-
同步屏障检查环节,如果存在同步屏障,则优先取异步消息作为优先处理的消息.
-
根据Message的when字段和当前时间对比,区分消息是被安排在未来的某个时间点处理,还是立即处理:
- 是delay消息则计算delay消息和当前消息的时间间距赋值给nextPollTimeoutMillis,以便在下次轮询时等待适当的时间
- 非delay消息说明真正获取到了准备被执行的消息。
至此,返回非delay消息给外部looper去处理,结束next流程.而无消息或者取到delay消息作为补充手段
处理异步消息的目的
确保在同步屏障存在时,仍然能够处理异步消息。它通过跳过同步消息,直接找到并处理异步消息。这种机制确保了高优先级任务(通常标记为异步)能够及时被处理,而不被同步屏障阻塞.
常见异步消息:
- UI线程中,动画通常需要优先处理,以确保流畅的用户体验。动画框架可能会使用异步消息来实现
- 绘图和渲染任务可能被标记为异步,以确保UI的及时更新
- 系统事件或应用程序事件可能被标记为异步,以确保它们能够及时被处理
- 用户输入事件(如触摸事件)可能被标记为异步,以确保快速响应用户操作
消息处理
- 修改阻塞标记mBlocked
- 让消息从链表内被移除,判断消息是否为队列内第一个消息分情况处理
- 消息标记被使用和处理,防止重复操作
- 消息返回给looper
从链表内移除消息,是确保消息在处理过程中不再与队列中的其他消息相关联,防止消息链表被意外修改,也便于消息回收和重用
空闲处理检查
为防止过程中有quit方法可能被调用过,需先判断执行dispose退出next方法.
仅当队列为空或队列中的第一条消息(可能是屏障)要在将来处理时,才会运行空闲句柄.
- 没有
idle handler需要执行,就设置阻塞状态mBlocked为true并开启下一轮synchronize代码块内for循环. - 存在就取
mIdleHandlers内容给mPendingIdleHandlers取执行
4. 空闲处理
遍历处理idle handler,并如果queueIdle方法返回false,执行后就可移除
5. 重置
- 重置pendingIdleHandlerCount:用于下一轮for循环能触发idle Handler
- 重置nextPollTimeoutMillis:在处理idle handler时,可能已传递新消息,因此下一轮for循环里面对nativePollOnce方法无需等待即可返回并再次查找待处理消息。
enqueueMessage:操作消息入链
-
容错处理
-
插入
-
作为链表头部,消息列表里无任何消息
-
非链表头部则向后查找
- 唤醒
同步屏障
为什么要同步屏障
主线程的消息队列待执行的消息非常多,怎么能保证绘制页面的消息优先得到执行,来尽力保证不卡顿呢?
往消息队列插入一个同步屏障消息(msg不为空并且target为空),作为异步消息。正常情况开发者也不能手动发送和移除同步屏障,因为它们都被hide注释了
对于异步消息,Looper会遍历消息队列找到异步消息执行,确保像刷新屏幕等高优任务及时得到执行。同步消息得不到处理,这就是为什么叫同步屏障的原因。
这里很好理解,UI相关的操作优先级最高,比如消息队列有很多没处理完的任务,这时候启动一个Activity,当然要优先处理Activity启动,然后再去处理其他的消息
怎样算同步屏障
创建Message对象,并设置target字段为null.
插入同步屏障:postSyncBarrier
创建Message对象,将mNextBarrierToken自增结果和形参when塞入arg1和when字段,从mMessages成员开始访问消息链表,在合适位置插入,并返回token
删除同步屏障:removeSyncBarrier
从mMessages成员开始访问消息链表,查找token值匹配的Message,回收消息
add/removeIdleHandler:idleHandler操作
关于idleHandler
原来是接口,实现queueIdle方法即可.前面讲next方法取消息能知,如果queueIdle方法返回false,执行后就可移除.所以不像Message或者Runnable一样,存在post跟remove对应的必要,除非需要提前移除.
调用时机
当出现消息链表空闲的时候,允许执行其他任务的一种机制。
怎样算空闲状态?在前面MessageQueue#next方法内得知:
- MessageQueue为空,没有消息
- MessageQueue中最近需要处理的消息,是个延迟消息(when>currentTime),需要滞后执行