Android Handler机制系列文章整体内容如下:本片文章的主要内容如下:
1. MessageQueue简介
2. MessageQueue类注释
3. MessageQueue成员变量
4. MessageQueue的构造函数
5. native层代码的初始化
6. IdleHandler简介
7. MessageQueue中的Message分类
一、MessageQueue简介
MessageQueue即消息队列,这个消息队列和上篇文章里面的Android Handler机制5之Message简介与消息对象对象池里面的消息对象池不是同一个东西。MessageQueue是一个消息队列,Handler将Message发送到消息队列中,消息队列会按照一定的规则取出要执行的Message。需要注意的是Java层的MessageQueue负责处理Java的消息,native也有一个MessageQueue负责处理native的消息,本文重点是Java层,所以暂时不分析native源码。
二、MessageQueue类注释
Low-level class holding the list of messages to be dispatched by a Looper. Messages are not added directly to a MessageQueue, but rather through Handler objects associated with the Looper. You can retrieve the MessageQueue for the current thread with Looper.myQueue().
翻译一下:
它是一个被Looper分发、低等级的持有Message集合的类。Message并不是直接加到MessageQueue的,而是通过Handler对象和Looper关联到一起。我们可以通过Looper.myQueue()方法来检索当前线程的MessageQueue。它是一个低等级的持有Messages集合的类,被Looper分发。Messages并不是直接加到MessageQueue的,而是通过Handler对象和Looper关联到一起。我们可以通过Looper.myQueue()方法来检索当前线程的
MessageQueue是Android消息机制的关键组成部分,负责存储和调度Message,与Looper配合实现消息的处理。
在Android中,MessageQueue是一个线程安全的可变消息队列,它包含了多个Message对象,这些Message对象会被放入到消息队列中,然后由Looper进行调度。
```java
// 用于标示消息队列是否可以被关闭,主线程的消息队列不可关闭
private final boolean mQuitAllowed;
// 该变量用于保存native代码中的MessageQueue的指针
private long mPtr; // used by native code
// 在MessageQueue中,所有的Message是以链表的形式组织在一起的,该变量保存了链表的第一个元素,也可以说它就是链表的本身
private Message mMessages;
// 当Handler线程处于空闲状态的时候(MessageQueue没有其他Message时),可以利用它来处理一些事物,该变量就是用于保存这些空闲时候要处理的事务
private final ArrayList
// 注册FileDescriptor以及感兴趣的Events,例如文件输入、输出和错误,设置回调函数,最后调用nativeSetFileDescriptorEvent注册到C++层中,当产生相应事件时,由C++层调用Java的DispathEvents,激活相应的回调函数
private SparseArray
// 用于保存将要被执行的IdleHandler
private IdleHandler[] mPendingIdleHandlers;
// 标示MessageQueue是否正在关闭。
private boolean mQuitting;
// 标示 MessageQueue是否阻塞
private boolean mBlocked;
// The next barrier token.
// Barriers are indicated by messages with a null target whose arg1 field carries the token.
// 在MessageQueue里面有一个概念叫做障栅,它用于拦截同步的Message,阻止这些消息被执行,只有异步Message才会放行。障栅本身也是一个Message,只是它的target为null并且arg1用于区分不同的障栅,所以该变量就是用于不断累加生成不同的障栅。
private int mNextBarrierToken;
```
dleHandler是MessageQueue内定义的一个接口,一般可用于做性能优化。当消息队列内没有需要立即执行的message时,会主动触发IdleHandler的queueIdle方法。IdleHandler是一个回调接口,当线程中的消息队列将要阻塞等待消息的时候,就会回调该接口,也就是说消息队列中的消息都处理完毕了,没有新的消息了,处于空闲状态时就会回调该接口。
作为Android开发者,我们知道Handler除了用于发送Message之外,还承载着执行具体业务逻辑的责任。handlerMessage(Message msg)方法用于处理这些业务逻辑。而IdleHandler在处理业务逻辑方面与Handler类似,但它只会在线程空闲的时候才执行业务逻辑的处理。这些业务通常包括一些不那么紧要或者不可预期的任务,例如垃圾回收(GC)。
IdleHandler接口定义如下:
```java
/**
* 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();
}
```
根据接口注释,IdleHandler接口包含一个抽象方法queueIdle。当线程空闲时,可以利用这个方法来处理一些业务逻辑。如果返回true,表示保持空闲处理器处于活动状态;如果返回false,则将其移除。这可能在消息队列中仍有待处理的消息时被调用,但它们都被安排在当前时间之后分发。
当消息队内所有的Message都执行完之后,IdleHandler的queueIdle()方法会被调用。该返回值为True的时候,IdleHandler会一直保持在消息队列中,False则会执行完该方法后移除IdleHandler。需要注意的是,当消息队列中还有其他Delay Message并且这些Message还没到被执行的时间的时候,由于线程是空闲的,所以IdleHandler也可能会被执行。
从源码可以看出,IdleHandler是一个简单的回调接口,内部只有一个带返回值的方法boolean queueIdle()。在使用时只需实现该接口并加入到MessageQueue中即可。以下是一个简单的示例代码:
```java
MessageQueue messageQueue = Looper.myQueue();
messageQueue.addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
// do something.
return false;
}
});
```
(二)添加IdleHandler:addIdleHandler(IdleHandler handler)
**
* 添加一个新的IdleHandler到此消息队列中。当IdleHandler的回调方法返回False时,该IdleHandler将在执行后立即被移除,或者您可以通过调用removeIdleHandler(IdleHandler handler)方法显式地移除指定的IdleHandler。
*
此方法可以从任何线程安全地调用。
*
* @param handler 要添加的IdleHandler
*/
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}
以下是重构后的内容:
(四) 删除IdleHandler的removeIdleHandler方法:
```java
/**
* 从之前添加到队列中的{@link IdleHandler}中移除一个{@link IdleHandler}。
* 如果给定的对象当前不在空闲列表中,则不执行任何操作。
*
* 此方法可以从任何线程安全地调用。
*
* @param handler 要从空闲列表中移除的IdleHandler。
*/
public void removeIdleHandler(@NonNull IdleHandler handler) {
synchronized (this) {
mIdleHandlers.remove(handler);
}
}
```
看先注释:从消息队列中移除一个之前添加的IdleHandler。如果该IdleHandler不存在,则什么也不做。移除IdleHandler的方法同样很简单,下一步同步处理然后直接mIdleHandlers.reomve(handler)就可以了。
七、MessageQueue中的Message分类:
在MessageQueue中,Message被分成3类,分别是:
1. 同步消息:正常情况下我们通过Handler发送的Message都属于同步消息,除非我们在发送的时候执行该消息是一个异步消息。同步消息会按顺序排列在队列中,除非指定Message的执行时间,否则Message会按顺序执行。
2. 异步消息:这类消息会在后台线程中执行,不会阻塞主线程。当接收到异步消息时,系统会自动为其分配一个新的线程来处理。异步消息的执行结果可以通过回调函数或者轮询的方式获取。
3. 障栅(Barrier):障栅是一种特殊的同步工具,它允许一组线程相互等待,直到所有线程都准备好继续执行。当所有线程都到达屏障时,屏障会释放它们,并使它们按顺序继续执行。
在往消息队列中发送异步消息时,我们需要通过构造函数 `public Handler(boolean async)` 将Handler指定为异步的。这样当Handler将Message加入到消息队列时,Message就会被设置为异步的。
除此之外,还有一种称为障栅(Barrier)的特殊Message,它的作用是用于拦截队列中的同步消息,放行异步消息。障栅具有如下特点:
- target属性为null,只有障栅的target可以为null。
- arg1属性用作障栅标识符,以区分不同的障栅。
类似于交警在道路拥堵时决定哪些车辆可以先通过,障栅的作用是允许异步消息先通过队列。要添加一个障栅,可以使用`postSyncBarrier()`方法。
以下是示例代码,演示如何使用异步消息和障栅来发送消息:
```java
// 初始化Handler为异步模式
Handler handler = new Handler(true);
// 创建一个障栅并设置标识符
String barrierIdentifier = "myBarrier";
Message barrierMessage = handler.obtainMessage();
barrierMessage.what = barrierIdentifier; // 设置标识符作为屏障的消息类型
barrierMessage.arg1 = 123; // 可以自定义障碍器的标识符参数
// 添加障栅到消息队列中
handler.sendMessageDelayed(barrierMessage, delayMillis); // delayMillis为延迟时间(毫秒)
// 其他同步操作...
// 当需要放行异步消息时发送屏障消息
Message asyncMessage = handler.obtainMessage();
asyncMessage.obj = myAsyncObject; // 设置异步对象
handler.sendMessageDelayed(barrierMessage, delayMillis); // 在屏障消息后延迟一定的时间再发送异步消息
```
上述代码中,首先我们创建了一个异步的Handler,然后定义了一个障栅并设置了标识符。接着,我们将障栅添加到消息队列中,以便在需要时拦截同步消息。当屏障消息被发送后,可以执行其他同步操作。最后,我们通过延迟一定时间后发送异步消息的方式实现了障栅的功能,确保同步操作完成后再处理异步消息。
**
* 在Looper的消息队列中发布一个同步屏障。
*
* 当消息队列遇到已发布的同步屏障时,消息处理将像往常一样进行。当遇到屏障时,队列中的后续同步消息会被挂起(阻止执行),直到通过调用{@link #removeSyncBarrier}并指定标识同步屏障的令牌来释放屏障。
* 此方法用于立即推迟所有随后发布的所有同步消息的执行,直到满足释放屏障的条件。异步消息(参见{@link Message#isAsynchronous})不受屏障的影响,将继续正常处理。
*
* 必须始终使用与{@link #removeSyncBarrier}相同令牌的此调用来确保消息队列恢复正常操作。否则,应用程序可能会挂起!
*
* @return 唯一标识屏障的令牌。必须将此令牌传递给{@link #removeSyncBarrier}以释放屏障。
* @hide
*/
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
在Android开发中,有时候我们需要在处理消息队列中的同步消息时,使用一个障栅(barrier)来阻止后续的消息被处理。障栅(barrier)是一种同步原语,它可以确保在特定条件下,消息队列中的某些消息不会被处理。当遇到障栅(barrier)时,消息队列中后续的同步消息会被阻塞,直到通过调用removeSyncBarrier()释放指定的障栅(barrier)。
要使用障栅(barrier),首先需要创建一个同步屏障(SyncBarrier)对象,并将其添加到消息队列中。然后,在处理消息时,检查是否遇到了障栅(barrier)。如果遇到了障栅(barrier),则停止处理后续的消息,等待障栅(barrier)被释放。最后,调用removeSyncBarrier()方法来释放障栅(barrier)。
使用相同的token去调用removeSyncBarrier(),以确保插入的障栅(barrier)和移除的是同一个。这样可以确保消息队列可以正常运行,否则应用程序可能会挂起。返回值是障栅(barrier)的唯一标识符,持有该标识符的token才能调用removeSyncBarrier()方法来真正地释放障栅(barrier)。
以下是一个简单的示例:
```java
// 创建一个同步屏障(SyncBarrier)对象
SyncBarrier syncBarrier = new SyncBarrier();
// 将同步屏障添加到消息队列中
Looper.getMainLooper().postSyncBarrier(syncBarrier);
// 在处理消息时,检查是否遇到了障栅(barrier)
if (syncBarrier.hasBeenReleased()) {
// 如果遇到了障栅(barrier),则停止处理后续的消息
break;
} else {
// 否则,继续处理后续的消息
}
// 当不再需要障栅(barrier)时,调用removeSyncBarrier()方法来释放障栅(barrier)
syncBarrier.release();
```
需要注意的是,这个方法会导致立即推迟所有后续发布的同步消息,直到满足释放指定的障栅(barrier)。而异步消息则不受障栅(barrier)的影响,并按照之前的流程继续处理。
```java
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
int token;
synchronized (this) {
// Step 1: Get the next barrier token and increment it.
token = mNextBarrierToken++;
// Step 2: Create a new message and set its properties.
Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
// Step 3: Find the first message in the queue with a timestamp greater than or equal to the barrier's timestamp.
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
// Move the pointer to the next message with a higher timestamp.
prev = p;
p = p.next;
}
}
// Step 4: Update the message's next and previous pointers based on the previous step's findings.
if (prev != null) {
// Invariant: p == prev.next
// The message's next message is p
msg.next = p;
// The message's previous message is prev
prev.next = msg;
} else {
// The message is at the head of the queue. Set its next message to itself and update the head of the queue.
msg.next = p;
mMessages = msg;
}
// Step 5: Return the barrier token as the result.
return token;
}
}
```
方法详解:
第一步:获取障栅的唯一标示,并将其自增1,作为下一个障栅的标示。这些唯一标示是从0开始,自加1的。
第二步:从消息对象池中获取一个Message,重置其when和arg1属性。将arg1设置为token的值,并通过msg.markInUse()方法标记该消息正在被使用。注意,这里并未给tareget赋值。
第三步:创建两个变量pre和p,用于第四步操作。其中p被赋值为mMessages,即消息队列的第一个元素。此时,p即为消息队列的第一个元素。
第四步:比较队列中的第一个Message的when属性与障栅的when属性,以确定障栅在整个消息队列中的位置。例如,如果障栅位于队列头部,则拦截后续的所有同步消息;如果位于队列第二个位置,则会放过第一个消息,然后拦截剩余的消息。依此类推。
第五步:将处理后的msg插入到消息队列中。
第六步:返回token。
从源码中可以看出,在将屏障插入队列时,首先根据when属性的比较结果将其插入到不同的位置。具体如下图所示:
蓝色部分为Message,红色部分为Barrier。
- 当Message.when < Barrier.when时,表示第一个Message的执行时间点在障栅之前。
- 当Message.when >= Barrier.when时,表示第一个Message的执行时间点在障栅之后。
在查看上述代码时,请注意一个事项:msg对象的target属性始终为null,因为在整个过程中从未对其进行过赋值。这也是在移除屏障时通过判断条件之一(即target是否为null)来判断的原因。
```java
/** * 移除一个同步屏障。
*
* @param token 同步屏障令牌,由{@link #postSyncBarrier}返回。
* @throws IllegalStateException 如果找不到该屏障。
*/
public void removeSyncBarrier(int token) {
// 从队列中移除同步屏障令牌。
// 如果队列不再被屏障阻塞,则唤醒它。
synchronized (this) {
Message prev = null;
// 获取消息队列的第一个元素
Message p = mMessages;
//遍历消息队列的所有元素,直到p.targe==null并且 p.arg1==token才是我们想要的障栅
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
if (p == null) {
throw new IllegalStateException("The specified message queue synchronization barrier token has not been posted or has already been removed.");
}
//是否需要唤醒
final boolean needWake;
//如果是障栅是不是第一个圆度
if (prev != null) {
//跳过障栅,将障栅的上一个元素的next指向障栅的next
prev.next = p.next;
//因为有元素,所以不需要唤醒
needWake = false;
} else {
//如果是第一个元素,则直接下消息队列中的第一个元素指向障栅的下一个即可
mMessages = p.next;
//如果消息队列中的第一个元素是null则说明消息队列中消息,所以需要唤醒
// needWake = mMessages == null || mMessages.target != null;
}
p.recycleUnchecked();
// If the loop is quitting then it is already awake. We can assume mPtr != 0 when mQuitting is false.
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
}
}
```
删除屏障(barrier)的方法相对简单。为了实现这个功能,我们需要不断地遍历消息队列,直到找到与指定的token相匹配的屏障。一旦找到了匹配的屏障,我们就可以将其从队列中移除。
具体步骤如下:
1. 遍历消息队列,检查每个屏障的token是否与要删除的屏障的token相匹配。
2. 如果找到匹配的屏障,将其从队列中移除。
3. 继续遍历队列,直到所有匹配的屏障都被移除或队列为空。
这种方法的时间复杂度取决于消息队列的大小,但在最坏的情况下,它仍然可以在线性时间内完成。