2024年字节跳动面试-Handler相关

4,465 阅读3分钟

问题:Handler.postDelayed原理,修改手机系统时间是否对延迟消息有影响。

回答:

修改系统时间对延迟消息无影响,原因是

  • 存入队列时,用到SystemClock.uptimeMillis() + delayMillis设置的msg.when (Handler.sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis))

  • 取出队列时,最终调用到了nativePollOnce(long ptr, int timeoutMillis),这里用到的 timeoutMillis也是 msg.when - SystemClock.uptimeMillis()。SystemClock.uptimeMillis()是自启动以来非睡眠正常运行时间的毫秒数, 是一个相对的时间,与修改手机系统时间SystemClock.currentTimeMillis()无关。修改手机系统时间不会改变自启动以来非睡眠正常运行时间的毫秒数,即不会改变SystemClock.uptimeMillis()的值。

所以,与修改系统时间对延迟消息无影响。

下面是取出队列时候的相关代码:

private native void nativePollOnce(long ptr, int timeoutMillis);
final long now = SystemClock.uptimeMillis();
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);
} 
nativePollOnce(ptr, nextPollTimeoutMillis);

加入队列时:Handler.postDelayed()的调用路径

一步一步跟一下Handler.postDelayed()的调用路径:

  1. Handler.postDelayed(Runnable r, long delayMillis)
  2. Handler.sendMessageDelayed(getPostMessage(r), delayMillis)
  3. Handler.sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)
  4. Handler.enqueueMessage(queue, msg, uptimeMillis)
  5. MessageQueue.enqueueMessage(msg,uptimeMillis)

取出队列时:MessageQueue.next()

@UnsupportedAppUsage
Message next() {
    // Return here if the message loop has already quit and been disposed.
    // This can happen if the application tries to restart a looper after quit
    // which is not supported.
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            // Try to retrieve the next message.  Return if found.
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                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);

需要注意的是ChatGPT的回答不完全正确,但有一定的参考意义。所以贴在了下面。

以下是ChatGPT的回答:

Handler.postDelayed 原理:

  • 当你调用 Handler.postDelayed(Runnable r, long delayMillis) 时,实际上是将一个消息(Message)放入消息队列,并设置一个执行时间,这个执行时间是当前时间加上你指定的延迟时间。
  • 消息队列会不断检查里面的消息,并且只有当当前系统时间到达或超过某条消息的执行时间时,消息才会被处理。
  • Looper 会持续地从消息队列中取出需要执行的消息,交给 Handler 处理。

修改系统时间对延迟消息的影响:

延迟消息的触发时间依赖于系统的当前时间(System.currentTimeMillis()),因此如果在消息被执行之前你修改了系统时间,会产生以下情况:

  1. 将系统时间向前调整(加快)

    • 如果你将系统时间调整为未来的某个时间点,则延迟消息会立即被处理,因为系统认为当前时间已经超出了消息的设定触发时间。
  2. 将系统时间向后调整(减慢)

    • 如果你将系统时间调整为过去的时间,则延迟消息会推迟执行。消息的设定触发时间会大于当前时间,系统会继续等待时间达到指定点再处理。

解决方案:

如果你想避免系统时间更改对延迟任务的影响,可以使用基于相对时间的时间源,例如:

  • SystemClock.uptimeMillis():它表示自设备启动以来的时间,排除了深度睡眠的时间。
  • SystemClock.elapsedRealtime():表示自设备启动以来的时间,包括设备处于休眠状态的时间。

这样做的好处是,系统时间的修改不会影响任务的执行,因为这些方法不依赖系统时间,而是基于设备的持续运行时间。

long delayMillis = 5000;  // 5秒延迟
handler.postAtTime(runnable, SystemClock.uptimeMillis() + delayMillis);

使用 SystemClock.uptimeMillis()SystemClock.elapsedRealtime() 可以确保你的延迟任务不会因为系统时间的修改而受到影响。

参考:

分析修改系统时间对Handler延迟消息是否有影响

面试:Handler.postDelayed()精确延迟指定时间的原理

www.kancloud.cn/alex_wsc/an…

www.cnblogs.com/jiy-for-you…

mazhuang.org/rtfsc-andro…