Android中消息队列延时不准确的原因,从系统时钟的精度到MessageQueue的机制

移动开发 Android
从系统时钟看已经过去1个小时了,但是计算now时因为uptimeMillis不包含休眠时的时间。如果now < msg.when 会判定Messsage还没有到执行时间,就不会从MessageQueue中取出并执行。

SystemClock系统时钟

SystemClock类是一个用于获取时间信息的核心类,为Android开发者提供了多种与时间相关的功能。

  1. 「类声明和结构」:

SystemClock是一个final类,继承自Object,不能被继承。

不可变类,提供了与时间相关的多种方法。

  1. 「主要方法」:

SystemClock.uptimeMillis():

返回从系统启动开始到现在的非休眠时间的毫秒数。

当系统进入深度睡眠(CPU休眠、屏幕休眠、设备等待外部输入)时,该时间停止。

SystemClock.elapsedRealtime() 和 SystemClock.elapsedRealtimeNanos():

返回从系统启动到现在的总时间,包括深度睡眠的时间。

两个方法返回的时间都是单调的,即使在省电模式下也会继续计时。

SystemClock.sleep(long):

类似于Thread.sleep(long),忽略了InterruptedException异常。

SystemClock.setCurrentTimeMillis(long):

「注意」:通常用于设置系统时间,需要系统权限。

将系统时间设置为指定的毫秒数,通常只在系统源码环境下使用,并且需要特定的编译和签名过程。

  1. 「其他注意事项」:

监听时间变化:

通过监听ACTION_TIME_TICK、ACTION_TIME_CHANGED和ACTION_TIMEZONE_CHANGED等广播,可以获取系统时间是否发生改变。

  1. 「权限要求」:

修改系统时间(如使用SystemClock.setCurrentTimeMillis(long))需要系统权限,应用需要在Android系统源码环境下进行编译和签名。

Hander延时消息延时会不准确

Hanler中发送消息(或者延时消息) 是通过SystemClock.uptimeMillis()为基准计算。

MessageQueue入队列时,计算Message.when以SystemClock.uptimeMillis()时间为基准。

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

从MessageQueue中消费Message,判断Message是否到了执行时间,也是以SystemClock.uptimeMillis()为基准。

//Looper.loop()循环
public static void loop() {
    for (;;) {
        Message msg = queue.next(); // might block
        msg.target.dispatchMessage(msg);
    }
}

//MessageQueue类的next方法
//msg.when > now时 才会将Message取出然后执行。
Message next() {
    for (;;) {
        ...
        synchronized (this) {
            // (1)计算当前时间
            final long now = SystemClock.uptimeMillis();
        
            // 返回取到的Message 
            if (msg != null) {
                //msg尚未到达触发时间,则计算新的阻塞超时时间nextPollTimeoutMillis,下次循环触发队列阻塞
                if (now < msg.when) {
                    // Next message is not ready.  Set a timeout to wake up when it is ready.
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    //从链表中移除该消息后,直接返回
                    // Got a message.
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    msg.markInUse();
                    return msg;
                }
            } 
            ...
    }
}

SystemClock.uptimeMillis()计算系统从开机到现在的时间,单位是毫秒。但是它不包括系统休眠的时间(cpu休眠、屏幕休眠等)。

当手机灭屏处于休眠状态的时间是不计算进SystemClock.uptimeMillis(),比如发送一个延时20分钟的Message消息,系统息屏后进入了深度睡眠(假设深度睡眠了1个小时),当进程苏醒后,这一个小时的时间是不计入(1)中的now。

final long now = SystemClock.uptimeMillis();

从系统时钟看已经过去1个小时了,但是计算now时因为uptimeMillis不包含休眠时的时间。如果now < msg.when 会判定Messsage还没有到执行时间,就不会从MessageQueue中取出并执行。

责任编辑:武晓燕 来源: 沐雨花飞蝶
相关推荐

2010-03-26 14:43:09

CentOS系统

2009-10-12 13:57:35

VMware虚拟机时钟

2024-10-16 15:11:58

消息队列系统设计

2021-03-11 06:01:41

Linux消息队列

2009-08-06 16:17:05

点对点消息队列

2020-09-27 07:44:08

RabbitMQ投递消息

2023-11-07 10:01:34

2013-04-11 12:40:16

Android消息机制

2021-05-07 22:17:06

手机定位信号

2011-12-02 10:58:06

数据结构Java

2011-05-31 11:55:00

Android 消息机制

2019-10-22 08:12:49

消息队列分布式系统

2017-05-15 19:40:40

AndroidIPC机制

2013-03-28 09:07:37

Android开发Intent机制

2022-06-13 11:05:35

RocketMQ消费者线程

2014-03-04 15:28:32

iOS开发消息传递机制

2021-12-06 23:00:36

CC++编程语言

2011-06-03 08:58:14

2021-11-24 08:33:09

Android广播机制应用程序

2010-04-21 14:49:13

Unix消息队列
点赞
收藏

51CTO技术栈公众号