当前位置: 首页 > news >正文

Handler postDelayed的实现原理

Handler postDelayed的实现原理

问题描述

Handler.postDelayed()的原理是如何保证延时执行的?
扩展:这样实现的好处是什么?

题目分析

猜测一下

以我们对Handler的了解,内部使用了Looper对消息队列进行循环获取执行,所以我们估计postDelayed()是Handler内部搞了一个定时器,
定时器到了delayed的时间就把消息加入到消息队列中,让looper在循环获取到该消息并执行。

真的是这样吗?如果不是,为什么?

我们来追溯一下源码

消息是怎样入队的?

首先调用的是sendMessageDelayed方法


public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {return sendMessageDelayed(getPostMessage(r), delayMillis);}
sendMessageDelayed计算了消息执行的准确时间,当前时间加上延时时间public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {if (delayMillis < 0) {delayMillis = 0;}return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);}public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {MessageQueue queue = mQueue;if (queue == null) {RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");Log.w("Looper", e.getMessage(), e);return false;}return enqueueMessage(queue, msg, uptimeMillis);}private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {msg.target = this;msg.workSourceUid = ThreadLocalWorkSource.getUid();if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);}  
Message的enqueueMessage方法boolean enqueueMessage(Message msg, long when) {if (msg.target == null) {throw new IllegalArgumentException("Message must have a target.");}if (msg.isInUse()) {throw new IllegalStateException(msg + " This message is already in use.");}synchronized (this) {if (mQuitting) {IllegalStateException e = new IllegalStateException(msg.target + " sending message to a Handler on a dead thread");Log.w(TAG, e.getMessage(), e);msg.recycle();return false;}msg.markInUse();msg.when = when;Message p = mMessages;boolean needWake;if (p == null || when == 0 || when < p.when) {// New head, wake up the event queue if blocked.msg.next = p;mMessages = msg;needWake = mBlocked;} else {// Inserted within the middle of the queue.  Usually we don't have to wake// up the event queue unless there is a barrier at the head of the queue// and the message is the earliest asynchronous message in the queue.needWake = mBlocked && p.target == null && msg.isAsynchronous();Message prev;for (;;) {prev = p;p = p.next;if (p == null || when < p.when) {break;}if (needWake && p.isAsynchronous()) {needWake = false;}}msg.next = p; // invariant: p == prev.nextprev.next = msg;}// We can assume mPtr != 0 because mQuitting is false.if (needWake) {nativeWake(mPtr);}}return true;}

查看源码我们发现:并不是像我们推测的那样使用定时器加入队列,而是简单计算了消息开始执行的时间之后就加入队列了。
MessageQueue中Message的结构就是一个简单的单向链表,只保存了链表头部的引用。
我们分析一下入队过程

if (p == null || when == 0 || when < p.when) {// New head, wake up the event queue if blocked.msg.next = p;mMessages = msg;needWake = mBlocked;} else {···}

如果链表头为空或者延时时间已经到了,则放到列表头,唤醒阻塞队列

for (;;) {prev = p;p = p.next;if (p == null || when < p.when) {break;}if (needWake && p.isAsynchronous()) {needWake = false;}}msg.next = p; // invariant: p == prev.nextprev.next = msg;

否则遍历链表,安装when的时间顺序插入消息,注意when = SystemClock.uptimeMillis() + delayMillis

Looper是如何出来延时消息的?

我们看看Looper的loop()方法

for (;;) {Message msg = queue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return;}...

原来是调用了MessageQueue的next方法,注释说明会阻塞。

我们看下MessageQueue的next方法里面做了啥?


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 iterationint 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);} else {// Got a message.mBlocked = false;if (prevMsg != null) {prevMsg.next = msg.next;} else {mMessages = msg.next;}msg.next = null;if (DEBUG) Log.v(TAG, "Returning message: " + msg);msg.markInUse();return msg;}} else {// No more messages.nextPollTimeoutMillis = -1;}// Process the quit message now that all pending messages have been handled.if (mQuitting) {dispose();return null;}// If first time idle, then get the number of idlers to run.// Idle handles only run if the queue is empty or if the first message// in the queue (possibly a barrier) is due to be handled in the future.if (pendingIdleHandlerCount < 0&& (mMessages == null || now < mMessages.when)) {pendingIdleHandlerCount = mIdleHandlers.size();}if (pendingIdleHandlerCount <= 0) {// No idle handlers to run.  Loop and wait some more.mBlocked = true;continue;}if (mPendingIdleHandlers == null) {mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];}mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);}// Run the idle handlers.// We only ever reach this code block during the first iteration.for (int i = 0; i < pendingIdleHandlerCount; i++) {final IdleHandler idler = mPendingIdleHandlers[i];mPendingIdleHandlers[i] = null; // release the reference to the handlerboolean keep = false;try {keep = idler.queueIdle();} catch (Throwable t) {Log.wtf(TAG, "IdleHandler threw exception", t);}if (!keep) {synchronized (this) {mIdleHandlers.remove(idler);}}}// Reset the idle handler count to 0 so we do not run them again.pendingIdleHandlerCount = 0;// While calling an idle handler, a new message could have been delivered// so go back and look again for a pending message without waiting.nextPollTimeoutMillis = 0;}}

原来在next方法中对链表头部的Message的执行时间进行了判断,如果当前时间小于msg.when,则计算阻塞时间,
然后在循环开始的时候判断如果这个Message有延迟,就调用nativePollOnce(ptr, nextPollTimeoutMillis)进行阻塞。
有兴趣的童鞋可以下Android源码看看native层的nativePollOnce是如何实现的,作用与object.wait()类似,只不过是使用了Native的方法对这个线程精确时间的唤醒。
唤醒之后loop()就能拿到对应的message了。

参考答案

1、比如postDelay()一个延时10秒钟的Runnable A、消息进队,MessageQueue调用nativePollOnce()阻塞,Looper阻塞;

2、紧接着post()一个Runnable B、消息进队,判断现在A时间还没到、正在阻塞,把B插入消息队列的头部(A的前面),然后调用nativeWake()方法唤醒线程;

3、MessageQueue.next()方法被唤醒后,重新开始读取消息链表,第一个消息B无延时,直接返回给Looper;

4、Looper处理完这个消息再次调用next()方法,MessageQueue继续读取消息链表,第二个消息A还没到时间,计算一下剩余时间(假如还剩9秒)继续调用nativePollOnce()阻塞;
直到阻塞时间到或者下一次有Message进队再次唤醒;

比我们的猜测好在哪里?

1、如果用我们的猜测方案,我们每添加一个延时消息就需要维护一个定时器,如果消息多耗费性能极大;

2、使用定时器到了延时时间再加入队列,如果队列中任务比较多,则延时的精度会大大降低,精度不如Google的方案。

相关文章:

Handler postDelayed的实现原理

Handler postDelayed的实现原理 问题描述 Handler.postDelayed()的原理是如何保证延时执行的&#xff1f; 扩展&#xff1a;这样实现的好处是什么&#xff1f; 题目分析 猜测一下 以我们对Handler的了解&#xff0c;内部使用了Looper对消息队列进行循环获取执行&#xff0…...

【数据结构】平衡二叉树

目录 一、平衡二叉树的介绍 二、平衡二叉树的插入 1、平衡二叉树的插入步骤 2、平衡二叉树的旋转 2.1左单旋 2.2右单旋 2.3左右双旋 2.4右左双旋 三、平衡二叉树的删除&#xff08;略&#xff09; 四、个人对平衡二叉树见解 五、平衡二叉树整体代码 一、平衡二叉树的…...

Minecraft服务端配置

✨✨前言 ✨✨ 我的世界大家肯定都不陌生&#xff0c;在网易拿下中国区的代理后&#xff0c;很多小伙伴也是都转向了网易版我的世界&#xff0c;网易版我的世界可以说已经做是的十分全面了&#xff0c;使用起来也十分方便&#xff0c;一部分小伙伴也是看重了网易庞大的玩家数量…...

yunUI组件库解析:图片上传与排序组件yImgPro

yunUI是笔者开源的微信小程序功能库。目前其中包含了一些复杂的功能组件。方便使用。未来它将分为组件、样式、js三者合为一体&#xff0c;但分别提供。 本文所用代码皆来源于组件库中的yImgPro组件。详细代码可至github查看。地址&#xff1a; yunUI 。 npm地址&#xff1a;yu…...

Java基础:回调函数

因为在看Android代码的时候发现了许多关于回调函数的知识, 所以去了解了一下. 对于我来说不太好懂, 因为我觉得看的那些博文的讲法对我来说很绕, 所以我在理解了之后想写一篇关于回调函数的博文来给和我一样理解能力稍差的人一点帮助. 回调函数的作用其实就是将需要这个功能的调…...

Springboot多环境配置

此文章是根据黑马程序员课程所做的笔记课程视频 多环境开发 ​ 什么是多环境&#xff1f;其实就是说你的电脑上写的程序最终要放到别人的服务器上去运行。每个计算机环境不一样&#xff0c;这就是多环境。常见的多环境开发主要兼顾3种环境设置&#xff0c;开发环境——自己用的…...

Java Number Math 类,超详细整理,适合新手入门

目录 一、什么是Java Number类&#xff1f; 二、Java Number类提供了哪些基本的数字操作&#xff1f; 三、什么是包装类&#xff1f; 所有的包装类都是抽象类 Number 的子类。 四、什么是Java Math 类 Test类案例&#xff1a;&#xff08;Math.PI 表示一个圆的周长与直径…...

俯瞰·明统系列·落霞与孤鹜齐飞、南征与北伐并举

尽江南百万兵&#xff0c;腰间宝剑血尤腥。 引言 元至正二十七年&#xff08;1367年&#xff09;四月&#xff0c;吴王朱元璋命中书右丞相徐达为征虏大将军、平章常遇春为副将军&#xff0c;率军25万由淮入河、北进中原&#xff08;第一次北伐&#xff09;。北伐中发布告北方官…...

Nodejs环境搭建和配置

Nodejs环境的搭建和配置 1、下载 官网&#xff1a;http://nodejs.cn/download/&#xff0c;选择windows64位 msi文件 2、安装和配置环境 双击安装之后&#xff0c;配置环境变量&#xff1a; ①系统变量那边创建NODE_PATH变量&#xff0c;值为nodejs文件夹的node_modules文…...

MybatisPlus------条件构造器Wrapper以及QueryWrapper用法(七)

MybatisPlus------条件构造器Wapper&#xff08;七&#xff09; Wrapper:条件构造器抽象类&#xff0c;最顶端父类 AbstarctWrapper&#xff1a;用于查询条件封装&#xff0c;生成sql的where条件。 QueryWrapper&#xff1a;查询条件封装&#xff08;可以用于查询、删除&#x…...

NetSuite Intercompany Framework 101

今朝&#xff0c;谈一谈Intercompany Framework&#xff0c;这是一个彰显NetSuite市场野心的基础功能框架。从20.2开始逐渐浮出水面&#xff0c;虽然经过过往的几个版本&#xff0c;不断推出组成功能&#xff0c;但目前仍然未见其全貌。 作为顾问&#xff0c;你必须关注它&…...

限时活动|凭徽章领披萨大奖,玩转Moonbeam治理论坛

动动手指&#xff0c;无需每天打卡&#xff0c;用刷手机的零碎时间领一份Web3惊喜&#xff01; 本次挑战的目标是鼓励大家参与社区治理、熟悉论坛操作。有关参与方式和原因的信息在Twitter上共享&#xff1a;有兴趣可以和ThinkWildCrypto一起探索论坛以解锁其功能、了解最近和正…...

Golang中struct{}和struct{}{}的区别你知道吗?

首先说下Golang中的结构体&#xff0c;结构体是由一系列具有相同类型或不同类型的数据构成的数据集合&#xff0c;Golang中使用关键字struct来创建一个结构体&#xff0c;语法如下&#xff1a;typeStudentstruct { Name string }下面定义一个Student结构体&#xff0c;例如&am…...

网络安全-信息收集- 谷歌浏览器插件收集信息,谷歌hacking搜索语法-带你玩不一样的搜索引擎

网络安全-信息收集- 谷歌浏览器插件收集信息&#xff0c;谷歌hacking搜索语法-带你玩不一样的搜索引擎 前言 一&#xff0c;我也是初学者记录的笔记 二&#xff0c;可能有错误的地方&#xff0c;请谨慎 三&#xff0c;欢迎各路大神指教 四&#xff0c;任何文章仅作为学习使用 …...

基础篇—一文掌握css的边框属性

CSS 边框属性 CSS边框属性允许你指定一个元素边框的样式和颜色。 1、边框样式 边框样式属性指定要显示什么样的边界。 border-style属性用来定义边框的样式 2、边框宽度 您可以通过 border-width 属性为边框指定宽度。 为边框指定宽度有两种方法:可以指定长度值,比如 2px…...

05服务发现:引入etcd服务注册中心

在分布式微服务架构中,服务注册发现组件(通常称为服务注册中心)往往有着举足轻重的作用,它的性能与稳定可能会直接影响到整个服务的状态,比如Spring Cloud中的Eureka、Dubbo中的Zookeeper等等,接下来我们就gRPC微服务中最常见的服务注册中心etcd,来讲述下两者在具体是怎…...

Pdfium.Net SDK 4.78.2704 完美Crack/Ptach

不限制时&#xff0c;/不限PDF体积、、、、、// version: 4.78.2704 | file size: 52.7 Mb Pdfium .Net SDK C# PDF 库 从头开始或从一堆扫描图像创建 PDF 编辑、合并、拆分和操作 PDF&#xff0c;提取文本和图像 嵌入独立的 Winforms 或 WPF PDF 查看器 支持&#xff1a;.Net…...

再学C语言38:指针操作

C提供了6种基本的指针操作 示例代码&#xff1a; #include <stdio.h>int main(void) {int arr[5] {1, 2, 3, 4, 5};int * p1, *p2, *p3;p1 arr; // 把一个地址赋给指针p2 &arr[2]; // 把一个地址赋给指针printf("指针指向的地址&#xff0c;指针指向地址中…...

【论文Word排版】使用多级列表设置论文序号

在Word中对论文进行排版 1.设置章节前面的序号 1.1 需求 通常情况下要求如下 一级标题“第一章 XXX”&#xff0c;然后是“1.1 研究意义”&#xff0c; “1.2 研究现状” 之前的处理方式都是手打&#xff0c;并没有借助word的多级列表实现。这次趁着写毕业论文研究了一下。…...

分支管理方案

背景 在工作的过程中&#xff0c;git管理方式已经成为每一个项目开发的基础&#xff0c;每个项目的开发都离不开git管理方式。 但是在使用的过程中&#xff0c;由于对git分支管理方案的了解不深&#xff0c;导致会出现分支管理不明确的情况。 本文主要是做科普作用&#xff…...

AI Agent在智能风控中的实战:多智能体欺诈检测与预警

AI Agent在智能风控中的实战:多智能体欺诈检测与预警 你有没有过明明是正常交易却被银行冻结账户的糟糕体验?或是听说过某电商平台上线新活动首日就被黑产团伙薅走数千万补贴的新闻?随着黑产欺诈向团伙化、专业化、动态化演进,传统依赖规则引擎、单模型机器学习的风控体系已…...

抖音数字资产管理方法论:构建个人内容沉淀系统的技术实践

抖音数字资产管理方法论&#xff1a;构建个人内容沉淀系统的技术实践 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback su…...

BLE蓝牙扫描深度剖析:扫描原理、核心参数、前后台差异

一、前言BLE设备交互分为两大角色&#xff1a;广播端&#xff08;外设Peripheral&#xff09;与扫描端&#xff08;中心Central&#xff09;。上一篇博客详解了四大广播模式&#xff0c;本文聚焦配套核心能力——BLE扫描机制。绝大多数蓝牙开发疑难问题&#xff1a;前台能扫后台…...

37家金融客户紧急启用的DeepSeek扫描辅助加固包(含未公开API调用密钥策略)

更多请点击&#xff1a; https://kaifayun.com 第一章&#xff1a;DeepSeek漏洞扫描辅助的背景与战略价值 近年来&#xff0c;大模型在安全领域的应用正从辅助问答向深度协同防御演进。DeepSeek系列模型凭借其开源、高推理精度及强代码理解能力&#xff0c;成为构建智能化漏洞…...

Windows Cleaner深度解析:5大核心模块彻底解决系统空间不足问题

Windows Cleaner深度解析&#xff1a;5大核心模块彻底解决系统空间不足问题 【免费下载链接】WindowsCleaner Windows Cleaner——专治C盘爆红及各种不服&#xff01; 项目地址: https://gitcode.com/gh_mirrors/wi/WindowsCleaner Windows Cleaner是一款完全免费开源的…...

LeaguePrank:5分钟打造个性化英雄联盟客户端,段位头像随心换!

LeaguePrank&#xff1a;5分钟打造个性化英雄联盟客户端&#xff0c;段位头像随心换&#xff01; 【免费下载链接】LeaguePrank 项目地址: https://gitcode.com/gh_mirrors/le/LeaguePrank 厌倦了千篇一律的英雄联盟客户端界面&#xff1f;想向好友展示王者段位却还在白…...

AhMyth位置跟踪:GPS定位与地理围栏技术深度解析

AhMyth位置跟踪&#xff1a;GPS定位与地理围栏技术深度解析 【免费下载链接】AhMyth Cross-Platform Android Remote Administration Tool | The only maintained version of AhMyth on github | A revival of the original repository at https://GitHub.com/AhMyth/AhMyth-An…...

模拟电路实现自主循线机器人:无MCU的硬件逻辑设计

1. 项目概述&#xff1a;用最纯粹的模拟电路&#xff0c;造一台会“思考”的机器人每次看到那些在赛道上灵巧穿梭的循线小车&#xff0c;你是不是也手痒&#xff0c;想自己动手做一个&#xff1f;但一听到“单片机”、“编程”、“Arduino”这些词&#xff0c;又觉得门槛太高&a…...

基于ISDN信令的来电语音播报系统:从原理到树莓派实现

1. 项目概述&#xff1a;一个基于ISDN的来电语音播报系统如果你家里或办公室里还有一台老式的ISDN路由器&#xff0c;别急着把它当电子垃圾处理掉。我最近就利用手头一台闲置的ISDN路由器&#xff0c;折腾出了一个挺有意思的小玩意儿&#xff1a;一个能自动识别来电号码&#x…...

低空旅游观光与低空通勤(eVTOL)运营管理与服务保障平台建设方案

本方案旨在为eVTOL载具构建集运营管理、空中交通管制、安全保障与乘客服务于一体的数字化平台。通过微服务架构、5G-A融合感知、空域网格化与零信任安全等核心技术&#xff0c;解决高密度飞行中的资源调度与安全冲突问题。目标实现毫秒级冲突解算与15分钟内快速周转&#xff0c…...