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 mIdleHandlers = new ArrayList();

// 注册FileDescriptor以及感兴趣的Events,例如文件输入、输出和错误,设置回调函数,最后调用nativeSetFileDescriptorEvent注册到C++层中,当产生相应事件时,由C++层调用Java的DispathEvents,激活相应的回调函数

private SparseArray mFileDescriptorRecords;

// 用于保存将要被执行的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. 继续遍历队列,直到所有匹配的屏障都被移除或队列为空。

这种方法的时间复杂度取决于消息队列的大小,但在最坏的情况下,它仍然可以在线性时间内完成。