Handler在有屏障消息和没有屏障消息时的区别

在Android的消息处理机制中,MessageQueue 扮演着关键角色,负责管理和调度消息的执行顺序。Looper 通过不断调用 MessageQueuenext() 方法,从队列中取出消息并分发给相应的 Handler 进行处理。理解 next() 方法在有屏障消息和没有屏障消息时的不同处理流程,对于优化应用性能、避免界面卡顿至关重要。

本文将详细解析 MessageQueue.next() 方法在 有屏障消息没有屏障消息 两种情况下的执行流程,帮助开发者深入理解消息调度机制。

1.消息类型简介

MessageQueue 中,消息主要分为以下几种类型:

  • 普通消息(Synchronous Messages):默认通过 Handler 发送的消息,按照发送顺序和时间戳(when)插入到消息队列中。
  • 异步消息(Asynchronous Messages):通过设置 Handler 为异步模式或手动标记消息为异步,具有较高的优先级,旨在绕过某些同步机制,快速处理重要任务。
  • 屏障消息(Barrier Messages):特殊类型的消息,用于在消息队列中设置屏障,阻止普通消息的处理,仅允许异步消息通过。通常由系统内部使用,开发者不直接操作。

2.MessageQueue.next() 方法概述

MessageQueuenext() 方法负责从队列中取出下一个要处理的消息,并返回给 Looper 进行处理。其基本逻辑包括:

  1. 阻塞等待:如果队列中没有可处理的消息,next() 方法会阻塞,直到有新消息到来或超时。
  2. 消息遍历:遍历消息队列,查找符合条件的消息进行处理。
  3. 消息优先级:根据消息的类型(普通消息、异步消息、屏障消息)和时间戳决定处理顺序。

下面将详细介绍 next() 方法在 没有屏障消息有屏障消息 两种情况下的执行流程。

3.没有屏障消息时的 next() 执行流程

当消息队列中 不存在屏障消息 时,next() 方法的执行流程相对简单,主要依据消息的时间戳和类型来决定处理顺序。

执行步骤

  1. 阻塞等待next() 方法首先通过 nativePollOnce() 阻塞等待,直到有新消息到来或超时。

    nativePollOnce(ptr, nextPollTimeoutMillis);
    

  2. 同步块访问:进入同步块 synchronized (this),确保线程安全地访问消息队列。

  3. 获取当前时间

    final long now = SystemClock.uptimeMillis();
    

  4. 遍历消息队列

    • 遍历所有消息,查找 when 时间戳小于等于当前时间的消息。
    • 异步消息优先:如果存在异步消息(msg.isAsynchronous() 返回 true),则优先处理异步消息。
    • 普通消息按顺序处理:如果没有异步消息,按照队列顺序处理普通消息。
  5. 处理消息

    • 异步消息:移除并返回异步消息进行处理。
    • 普通消息:移除并返回普通消息进行处理。
  6. 等待机制:如果没有符合条件的消息,更新 nextPollTimeoutMillis,继续等待。

    简化代码示例
// MessageQueue.java
Message next() {
    while (true) {
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;

            Message nextMsg = null;
            long nextTime = Long.MAX_VALUE;
            Message asyncMsg = null;

            while (msg != null) {
                if (msg.when <= now) {
                    if (msg.isAsynchronous()) {
                        asyncMsg = msg;
                        break; // 优先处理异步消息
                    } else if (nextMsg == null) {
                        nextMsg = msg; // 记录第一个可处理的普通消息
                    }
                } else {
                    if (msg.when < nextTime) {
                        nextTime = msg.when; // 记录下一个消息的处理时间
                    }
                }
                msg = msg.next;
            }

            if (asyncMsg != null) {
                removeMessage(asyncMsg);
                return asyncMsg;
            }

            if (nextMsg != null) {
                removeMessage(nextMsg);
                return nextMsg;
            }

            long waitTime = nextTime - now;
            if (waitTime > 0) {
                nextPollTimeoutMillis = (int) Math.min(waitTime, Integer.MAX_VALUE);
            } else {
                nextPollTimeoutMillis = -1;
            }
        }
    }
}

4.有屏障消息时的 next() 执行流程

当消息队列中 存在屏障消息 时,next() 方法需要额外处理,以确保屏障消息的存在阻止普通消息的处理,仅允许异步消息通过。

执行步骤

  1. 阻塞等待:与无屏障消息时相同,通过 nativePollOnce() 阻塞等待。

    nativePollOnce(ptr, nextPollTimeoutMillis);
  2. 同步块访问:进入同步块 synchronized (this)

  3. 获取当前时间

    final long now = SystemClock.uptimeMillis();
  4. 检查是否存在屏障消息

    Message msg = mMessages;
    if (msg != null && msg.target == null) {
        // 存在屏障消息
    }
    
  5. 跳过屏障消息,查找异步消息

    Message prevMsg = null;
    do {
        prevMsg = msg;
        msg = msg.next;
    } while (msg != null && !msg.isAsynchronous());
    
    • 遍历:从屏障消息后开始,遍历消息队列,查找第一个异步消息。
    • 目标:确保只有异步消息能够被处理,普通消息被屏障阻止。
  6. 处理消息

    • 异步消息:如果找到异步消息且 when 时间戳已到,移除并返回该消息进行处理。
    • 无异步消息:继续等待,普通消息被屏障阻止。
  7. 等待机制:如果没有找到可处理的异步消息,更新 nextPollTimeoutMillis,继续等待。

简化代码示例

// MessageQueue.java
Message next() {
    while (true) {
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;

            // 检查是否遇到屏障消息
            if (msg != null && msg.target == null) {
                // 遇到屏障消息,跳过屏障,查找异步消息
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }

            if (msg != null) {
                if (now < msg.when) {
                    // 消息未到处理时间,继续等待
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // 移除消息并返回
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    msg.markInUse();
                    return msg;
                }
            } else {
                // 队列中无可处理消息,继续等待
                nextPollTimeoutMillis = -1;
            }
        }
    }
}

5.关键代码解析

以下是 MessageQueue.next() 方法在有屏障消息和没有屏障消息时的关键代码片段解析。

检查屏障消息

Message msg = mMessages;
if (msg != null && msg.target == null) {
    // 遇到屏障消息,跳过屏障,查找异步消息
    do {
        prevMsg = msg;
        msg = msg.next;
    } while (msg != null && !msg.isAsynchronous());
}
  • 条件判断msg.target == null 用于判断是否为屏障消息。
  • 遍历查找:从屏障消息后开始,查找第一个异步消息。

处理消息

if (msg != null) {
    if (now < msg.when) {
        // 消息未到处理时间,继续等待
        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
    } else {
        // 移除消息并返回
        mBlocked = false;
        if (prevMsg != null) {
            prevMsg.next = msg.next;
        } else {
            mMessages = msg.next;
        }
        msg.next = null;
        msg.markInUse();
        return msg;
    }
} else {
    // 队列中无可处理消息,继续等待
    nextPollTimeoutMillis = -1;
}
  • 时间判断:如果消息的 when 时间戳未到,则更新等待时间。
  • 消息移除:将处理的消息从队列中移除,并返回该消息进行处理。

6.示例流程

通过一个具体的示例,说明在有屏障消息和没有屏障消息时,next() 方法如何处理消息。

场景描述

假设消息队列中存在以下消息顺序:

  1. 普通消息Awhen = 1000ms
  2. 屏障消息Btarget = nullwhen = 1500ms
  3. 异步消息Cwhen = 1000ms
  4. 普通消息Dwhen = 2000ms
  5. 异步消息Ewhen = 1500ms

执行流程

没有屏障消息时
  1. 调用 next() 方法

    • nativePollOnce() 阻塞等待,直到有消息到来或超时。
  2. 同步块内部

    • 获取当前时间 now = 1000ms
    • 获取队列中的第一个消息 msg = 普通消息A
  3. 检查是否为屏障消息

    • msg.target != null,即普通消息A,不是屏障消息。
  4. 处理普通消息A

    • now >= msg.when,即 1000ms >= 1000ms
    • 移除并返回消息A进行处理。
  5. 下次调用 next() 方法

    • 获取当前时间 now = 1000ms
    • 获取队列中的第一个消息 msg = 异步消息C
  6. 处理异步消息C

    • now >= msg.when,即 1000ms >= 1000ms
    • 移除并返回消息C进行处理。
有屏障消息时
  1. 调用 next() 方法

    • nativePollOnce() 阻塞等待。
  2. 同步块内部

    • 获取当前时间 now = 1500ms
    • 获取队列中的第一个消息 msg = 屏障消息B
  3. 检查是否为屏障消息

    • msg.target == null,即屏障消息B。
  4. 跳过屏障消息,查找异步消息

    • 遍历消息队列,找到异步消息E。
  5. 处理异步消息E

    • now >= msg.when,即 1500ms >= 1500ms
    • 移除并返回消息E进行处理。
  6. 下次调用 next() 方法

    • 获取当前时间 now = 2000ms
    • 获取队列中的第一个消息 msg = 普通消息D
  7. 检查是否为屏障消息

    • msg.target != null,即普通消息D,不是屏障消息。
  8. 处理普通消息D

    • now >= msg.when,即 2000ms >= 2000ms
    • 移除并返回消息D进行处理。

处理顺序

  • 没有屏障消息时

    1. 异步消息C
    2. 普通消息A
  • 有屏障消息时

    1. 异步消息E
    2. 普通消息D

屏障消息B 仅起到了阻止普通消息A和C的作用,使得异步消息E能够优先执行。

7.总结

MessageQueue.next() 方法在有屏障消息和没有屏障消息时的处理流程存在显著区别:

  • 没有屏障消息时

    • 消息按照时间戳和类型(异步消息优先)顺序执行。
    • 异步消息即使在队列中后于普通消息,也会优先处理。
  • 有屏障消息时

    • 屏障消息阻止普通消息的处理,仅允许异步消息通过。
    • 当遇到屏障消息,next() 方法会跳过普通消息,优先查找并处理异步消息。
    • 屏障消息确保关键任务(如UI绘制)能够及时执行,避免被普通消息阻塞。

理解 MessageQueue.next() 方法在不同情况下的执行流程,有助于开发者更有效地管理消息队列,优化应用性能,提升用户体验。在实际开发中,合理使用异步消息和屏障消息,可以显著改善应用的响应速度和流畅度。

🌟 关注我的CSDN博客,收获更多技术干货! 🌟

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dm菜鸟编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值