当前位置: 首页 > 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…...

谷歌浏览器插件

项目中有时候会用到插件 sync-cookie-extension1.0.0&#xff1a;开发环境同步测试 cookie 至 localhost&#xff0c;便于本地请求服务携带 cookie 参考地址&#xff1a;https://juejin.cn/post/7139354571712757767 里面有源码下载下来&#xff0c;加在到扩展即可使用FeHelp…...

【网络】每天掌握一个Linux命令 - iftop

在Linux系统中&#xff0c;iftop是网络管理的得力助手&#xff0c;能实时监控网络流量、连接情况等&#xff0c;帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...

基于Flask实现的医疗保险欺诈识别监测模型

基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施&#xff0c;由雇主和个人按一定比例缴纳保险费&#xff0c;建立社会医疗保险基金&#xff0c;支付雇员医疗费用的一种医疗保险制度&#xff0c; 它是促进社会文明和进步的…...

微信小程序 - 手机震动

一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注&#xff1a;文档 https://developers.weixin.qq…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互

引擎版本&#xff1a; 3.8.1 语言&#xff1a; JavaScript/TypeScript、C、Java 环境&#xff1a;Window 参考&#xff1a;Java原生反射机制 您好&#xff0c;我是鹤九日&#xff01; 回顾 在上篇文章中&#xff1a;CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...

linux 下常用变更-8

1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行&#xff0c;YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID&#xff1a; YW3…...

12.找到字符串中所有字母异位词

&#x1f9e0; 题目解析 题目描述&#xff1a; 给定两个字符串 s 和 p&#xff0c;找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义&#xff1a; 若两个字符串包含的字符种类和出现次数完全相同&#xff0c;顺序无所谓&#xff0c;则互为…...

蓝桥杯3498 01串的熵

问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798&#xff0c; 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码

目录 一、&#x1f468;‍&#x1f393;网站题目 二、✍️网站描述 三、&#x1f4da;网站介绍 四、&#x1f310;网站效果 五、&#x1fa93; 代码实现 &#x1f9f1;HTML 六、&#x1f947; 如何让学习不再盲目 七、&#x1f381;更多干货 一、&#x1f468;‍&#x1f…...

SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题

分区配置 (ptab.json) img 属性介绍&#xff1a; img 属性指定分区存放的 image 名称&#xff0c;指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件&#xff0c;则以 proj_name:binary_name 格式指定文件名&#xff0c; proj_name 为工程 名&…...