MessageQueue 到底是什么?
从 MessageQueue 的创建说起
1 | private Looper(boolean quitAllowed) { |
MessageQueue 就是在 Looper 的构造方法里创建的,一个 Looper 就对应了一个 MessageQueue。
MessageQueue 如何实现线程间的数据隔离
线程是不持有系统资源的进程,所以同一个进程中的线程是共用的同一个进程持有的内存,说人话就是进程中持有的内存中的变量和数据每个线程都可以直接读取,MessageQueue 是存放线程要处理的消息的,我们当然不希望它是进程持有的线程之间共享的,不能被其他的线程所干扰,换句话说 MessageQueue 必须是线程隔离的
android.os.Looper#prepare(boolean)
1 | private static void prepare(boolean quitAllowed) { |
注意 Looper 是一个静态类非一个实例,在 Looper 的 prepare 阶段,会去 new Looper(quitAllowed),并将其放入 ThreadLocal,这样就让 Looper 成为了 线程变量,而 MessageQueue 由 Looper 创建并持有,所以 MessageQueue 自然也成了 线程变量,这样就实现了每个线程有自己独立的 Looper 和 MessageQueue 实例,且相互隔离。
MessageQueue
是每个线程独有的。每个线程都拥有自己的消息队列,因此在不同的线程之间无法直接共享消息队列。消息是在一个线程中创建和发送到该线程的消息队列,然后由该线程的 Looper
从队列中取出并处理。
1 | `ThreadLocal` 是一个线程级别的存储,它在每个线程中维护一个独立的存储空间(`ThreadLocalMap`),每个存储空间使用 `ThreadLocal` 对象作为键。不同线程的存储空间互不干扰,实现了线程间的数据隔离;但在同一个线程内,`ThreadLocal` 对象可以共享,对应的数据副本在不同方法间保持一致。、 |
MessageQueue 同步屏障
试想一种情况 MessageQueue 需要处理
android.os.MessageQueue#next 中的另一种执行逻辑
1 |
|
我们重点看这一段
1 | if (msg != null && msg.target == null) { |
如果 msg != null,而 msg.target == null 的时候,android.os.MessageQueue#next 执行了完全不同的另一种逻辑,target 就是 msg 的目标 handler,也就是说如果 msg 没有目标 handler 的时候,那么 msg 就是一个屏障消息,android.os.MessageQueue#next 就会进入无限循环读取异步消息的逻辑
从这里我们知道 MessageQueue 提供了一个屏障,这个屏障可以让 MessageQueue 越过所有同步消息优先执行异步消息
我们看看这个屏障该如何升起与取消
简单的发送一个 msg.target == null 的消息升起同步屏障
可以简单的发送一个 msg.target == null 的消息来升起这个屏障吗?
尝试一下你就会发现出现了”Message must have a target.”的异常
android.os.MessageQueue#enqueueMessage msg.target == null 的危险性与抛出的异常
1 | boolean enqueueMessage(Message msg, long when) { |
我们发现 android.os.MessageQueue#enqueueMessage 第一步就是检查 msg.target 是否为 null,msg.target == null 极度危险,一旦消息没有正确的被处理,会导致整个 MessageQueue 进入异步消息的死循环无法退出,因此 消息屏障的触发与取消必须被管控起来
严格管控下的同步屏障的触发
1 | /** |
这段代码是 Android 消息机制中的一部分,用于实现同步屏障(Sync Barrier)。同步屏障是一种机制,可以用来控制消息队列中的消息执行顺序,特别是用于确保后续的同步消息在某个条件满足之前被阻塞执行。下面对这段代码进行分析:
postSyncBarrier()
方法:- 这个方法是向消息队列中添加一个同步屏障。
- 同步屏障是一种特殊的消息,它会阻塞后续的同步消息的执行,直到同步屏障被释放。
- 该方法返回一个用于标识同步屏障的 token,这个 token 在稍后调用
removeSyncBarrier()
方法时需要使用。
postSyncBarrier(long when)
方法:- 这个方法是
postSyncBarrier()
的内部实现。 - 该方法会创建一个同步屏障消息,设置其触发时间(when)和一个唯一的 token。
- 同步屏障消息将被插入消息队列中,并根据触发时间排序。
- 这个方法是
在插入同步屏障消息时:
- 遍历消息队列,找到合适的位置插入同步屏障消息,以保持消息队列的顺序。
- 如果同步屏障消息需要插入的位置在已有消息之后,将同步屏障消息插入到该位置之后。
- 如果同步屏障消息需要插入的位置在已有消息之前,将同步屏障消息作为新的头部消息。
总结起来,这段代码实现了向消息队列中插入同步屏障消息的功能。同步屏障消息的作用是阻塞后续的同步消息的执行,直到满足某个条件后释放同步屏障。这种机制可以用于控制消息队列中消息的执行顺序,确保在特定条件满足之前某些消息不被执行。
严格管控下的同步屏障的取消
1 | /** |
这段代码是用于移除同步屏障(Sync Barrier)的逻辑。它会从消息队列中移除指定的同步屏障消息,并在必要时唤醒消息队列,以继续处理后续的消息。下面对这段代码进行分析:
removeSyncBarrier(int token)
方法:- 这个方法用于移除同步屏障。
- 它接受一个参数
token
,即之前调用postSyncBarrier()
方法返回的标识同步屏障的 token。 - 如果指定的同步屏障消息被找到并移除,将会在必要时唤醒消息队列。
在移除同步屏障消息时:
- 遍历消息队列,寻找包含指定 token 的同步屏障消息。
- 如果找到了匹配的同步屏障消息,将其从消息队列中移除。
- 如果在移除同步屏障消息后,消息队列不再被其他消息阻塞,会将队列唤醒,以继续处理后续的消息。
recycleUnchecked()
方法:- 在移除同步屏障消息后,调用这个方法将消息对象回收,以便释放资源。
总结起来,这段代码实现了移除同步屏障消息的功能。当同步屏障条件满足后,通过调用 removeSyncBarrier()
方法来移除同步屏障消息,从而解除对后续同步消息的阻塞。这个机制可以用于控制消息队列中消息的执行顺序,确保在特定条件满足后执行后续的同步消息。
从 ViewRootImpl 看同步屏障的使用
android.view.ViewRootImpl#scheduleTraversals
1 |
|
同步屏障的触发
1 | mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); |
再看一下发送消息的逻辑
android.view.Choreographer#postCallback
1 | /** |
android.view.Choreographer#postCallbackDelayed
1 | /** |
重点: android.view.Choreographer#postCallbackDelayedInternal
1 | private void postCallbackDelayedInternal(int callbackType, |
我们看异步消息的发送逻辑
1 | Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); |
android.view.ViewRootImpl#unscheduleTraversals
1 | void unscheduleTraversals() { |
android.view.ViewRootImpl#unscheduleTraversals 的调用时机比较有意思,这里暂不做更多的分析
Skipped 30 frames!
错误信息
在进行 UI 更新的时候,如果 UI 线程忙碌,主线程有时会抛出异常信息:Skipped 30 frames! The application may be doing too much work on its main thread.
错误原因
这是因为触发屏障的实际操作是发送一个 target 为 null 的 msg,但是如果这个 target 为 null 的消息被前面的耗时消息所耽误而一直没能执行,导致后面不断发送来的需要立即执行的异步消息都被耽误,触发同步屏障之后,系统发现,本该立即执行的异步消息已经积累了30帧只会,就会报出这个主线程忙碌的错误
改进方法
罪魁祸首就是那个耽误了 target == null 的同步屏障 msg 执行的 msg,这个同步消息的执行如此之耗时,以至于我们必须考虑对其优化或者考虑是不是适合放在主线程
可以放到其他线程去执行
MessageQueue 中的 synchronized
android.os.MessageQueue#next 中的 synchronized
1 |
|
第一处
synchronized
用于同步代码块的范围涵盖了整个 next()
方法。具体来说,这段代码实现了一个消息循环(message loop)用于处理消息队列中的消息。在多线程环境下,有多个线程可能会调用 next()
方法来获取下一个消息,因此需要确保对共享资源的访问是安全的。
第二处
mIdleHandlers.remove(idler)
:这行代码从 mIdleHandlers
集合中移除一个 IdleHandler
。由于多个线程可能同时访问和修改 mIdleHandlers
,因此需要确保这个操作是原子的,以避免不一致或意外的结果。
其他函数
都是 synchronized 保护下的
MessageQueue 是 Android 框架中用于处理消息传递和线程通信的关键组件,多个线程可能会同时访问和修改消息队列,因此需要使用同步机制来避免竞态条件和其他线程安全问题。
MessageQueue 中的 IdleHandler
使用与意义
IdleHandler
是 Android 消息传递机制中的一个重要概念,它允许你在消息队列空闲时执行一些额外的操作。
- 使用: 你可以通过
MessageQueue
的addIdleHandler()
方法将一个或多个IdleHandler
添加到消息队列中。 - 意义:
IdleHandler
允许你在消息队列空闲时执行一些轻量级的任务,这些任务通常是一些不需要立即处理、不会阻塞主线程的操作。常见的用例包括资源回收、后台数据同步、性能优化等。通过利用空闲时间执行这些任务,可以提高应用的性能和资源利用率。 - 执行时机:
IdleHandler
的queueIdle()
方法在消息队列没有即时任务需要处理时调用。如果queueIdle()
返回true
,该IdleHandler
将继续保持在队列中,以便在下一次空闲时调用;如果返回false
,则该IdleHandler
将从队列中移除。
为什么会有多个 IdleHandler
- 功能分离: 不同的
IdleHandler
可以用于执行不同类型的任务,如资源回收、后台数据同步、性能优化等。通过将不同的任务逻辑分离到不同的IdleHandler
中,可以使代码更加模块化和可维护。 - 任务优先级: 不同的
IdleHandler
可以根据优先级来执行任务。高优先级任务可以通过将对应的IdleHandler
添加到队列中,确保在空闲时尽快执行。低优先级任务则可以延迟到更空闲的时候执行。 - 动态注册和注销: 多个
IdleHandler
允许开发者在不同的时刻动态地注册和注销任务。这使得可以根据应用程序的状态和需求来动态地调整任务的执行。 - 任务复用: 如果有多个相似的任务需要在空闲时执行,可以通过不同的
IdleHandler
实现任务的复用,避免重复编写类似的代码。 - 提高性能: 通过将不同的任务拆分到多个
IdleHandler
中,可以减少单个IdleHandler
的负载,从而提高任务的执行效率。
关键行为分析
1 |
|
mPendingIdleHandlers 与 mIdleHandlers
- mPendingIdleHandlers:
- 类型:
IdleHandler[]
- 作用:用于存储当前等待执行的空闲时处理对象(
IdleHandler
)数组。在消息队列空闲时,这些处理会被调用,以执行额外的任务。 - 使用场景:用于临时存储等待执行的空闲时处理,直接与循环内部逻辑相关。
- 类型:
- mIdleHandlers:
- 类型:
ArrayList<IdleHandler>
- 作用:用于存储注册的空闲时处理对象。开发者可以将多个
IdleHandler
添加到这个列表中,以便在消息队列空闲时执行不同的任务。 - 使用场景:用于持久存储注册的空闲时处理,可以在任何时候添加或移除
IdleHandler
。
- 类型:
总结区别:
mPendingIdleHandlers
是一个数组,用于存储当前等待执行的空闲时处理对象。它是循环内部临时使用的,用于遍历调用每个等待执行的空闲时处理。mIdleHandlers
是一个列表,用于持久存储注册的空闲时处理对象。开发者可以随时将IdleHandler
添加到列表中,以便在消息队列空闲时执行不同的任务。
给 PendingIdleHandlers 分配新的数组空间
1 | if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { |
这段代码的作用是在消息队列空闲时,检查是否需要运行空闲时处理。如果当前没有消息或者队列中的第一个消息的处理时间还未到来,并且之前的 pendingIdleHandlerCount
小于 0,那么它会获取当前注册的空闲时处理的数量,并将其赋值给 pendingIdleHandlerCount
。这样,当队列为空或者第一个消息处理时间未到来时,代码会准备好运行已注册的空闲时处理。通常情况下,这个检查用于确保空闲时处理在适当的时机被调度执行。
1 | if (pendingIdleHandlerCount <= 0) { |
这段代码的作用是在消息队列空闲时,如果没有等待执行的空闲时处理,就将消息队列标记为被阻塞状态,并继续等待更多的消息或任务进入队列。这个逻辑用于优化资源管理,确保在没有即时任务需要处理时,程序仍然能够保持运行,以便在有任务时能够立即执行。
1 | if (mPendingIdleHandlers == null) { |
注意:隐含的条件是 pendingIdleHandlerCount <= 0 才能走到这里,所以判断条件其实是 pendingIdleHandlerCount <= 0 && mPendingIdleHandlers == null,通常表示这是第一次运行空闲时处理
如果 mPendingIdleHandlers
为 null
,则进入条件判断。在这里,使用 Math.max(pendingIdleHandlerCount, 4)
来计算数组的长度,其中 pendingIdleHandlerCount
是等待执行的空闲时处理的数量。如果等待执行的处理数量小于 4,则数组长度取值为 4,否则取值为 pendingIdleHandlerCount
PendingIdleHandlers 赋值
1 | mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); |
运行空闲时处理 PendingIdleHandlers
1 | // Run the idle handlers. |
注意:这里注释中的第一次迭代是指的消息队列第一次处理完所有已经在队列中的消息的那一次迭代,是想说明在后续的迭代中,如果消息队列仍然处于空闲状态,那么这段代码块不会再执行,因为它只在消息队列刚刚变为空闲时运行。
队列的唤醒与阻塞
android.os.MessageQueue#enqueueMessage
1 | boolean enqueueMessage(Message msg, long when) { |
在这段代码中,mBlocked
在两个地方起到了不同的作用:
第一个 mBlocked 作用:
1
needWake = mBlocked;
在这里,
mBlocked
被赋值给变量needWake
。这个操作是为了判断是否需要唤醒事件队列(消息循环)。具体情况如下:- 如果
mBlocked
为true
,表示事件队列当前正处于阻塞状态,即没有立即需要处理的消息。在这种情况下,如果新消息的插入导致事件队列不再阻塞,就需要唤醒事件队列,以便消息循环继续执行。 - 如果
mBlocked
为false
,表示事件队列没有阻塞,新消息的插入不会改变这个状态,因此不需要唤醒。
- 如果
第二个 mBlocked 作用:
1
needWake = mBlocked && p.target == null && msg.isAsynchronous();
在这里,
mBlocked
参与了判断条件。这个条件用于判断是否需要唤醒事件队列,以提醒消息循环处理新消息。具体情况如下:- 如果
mBlocked
为true
,表示事件队列当前正处于阻塞状态。然后,进一步判断消息队列中是否有异步消息,并且插入的新消息也是异步消息,那么需要唤醒事件队列,以便消息循环能够立即处理这个异步消息。
- 如果
综上所述,mBlocked
在这段代码中的两个地方都与判断是否需要唤醒事件队列有关。第一个地方是用来判断是否需要在新消息插入时唤醒事件队列,以便消息循环继续执行。第二个地方是在特定条件下,判断是否需要唤醒事件队列来处理异步消息。
android.os.MessageQueue#next
在提供的代码片段中,有三处不同的地方使用了 mBlocked
参数,并且它们在不同的上下文中起到了不同的作用。以下是每个位置的详细分析:
第一处 mBlocked 作用:
1 | mBlocked = false; |
在这里,mBlocked
被设置为 false
,表示消息队列不再处于阻塞状态。这是在找到了一个准备好被处理的消息后执行的操作。通过将 mBlocked
设置为 false
,消息循环可以继续处理消息,而不需要等待。
第二处 mBlocked 作用:
1 | mBlocked = true; |
在这里,mBlocked
被设置为 true
,表示消息队列当前处于阻塞状态。这是在判断没有可处理的消息,并且没有要运行的空闲处理程序时执行的操作。通过将 mBlocked
设置为 true
,消息循环进入了等待状态,等待新的消息或者空闲处理程序的到来。
第三处 mBlocked 作用:
1 | mBlocked = true; |
这里的 mBlocked
也被设置为 true
,与第二处的作用相同。这是在消息队列处理完所有的空闲处理程序之后,仍然没有要运行的空闲处理程序时执行的操作。通过将 mBlocked
设置为 true
,消息循环会继续等待,直到有新的消息到达或者空闲处理程序需要运行。
总结来说,mBlocked
在这段代码中的三处不同的作用是:
- 在找到一个准备好被处理的消息后,将其设置为
false
,使得消息循环可以继续处理消息。 - 在没有可处理的消息且没有要运行的空闲处理程序时,将其设置为
true
,使得消息循环进入等待状态。 - 在所有空闲处理程序都被处理后,仍然没有要运行的空闲处理程序时,将其设置为
true
,继续等待。
通过这样的设置,消息循环能够根据不同的情况来控制阻塞和等待状态,以便有效地处理消息和任务。