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

Android中级——消息机制

消息机制

  • 概念
  • ThreadLocal
  • MessageQueue
  • Looper
  • Handler
  • runOnUiThread()

概念

  • MessageQueue:采用单链表的方法存储消息列表
  • Looper:查询MessageQueue是否有新消息,有则处理,无则等待
  • ThreadLocal:用于Handler获取当前线程的Looper
  • 线程默认没有Looper,当使用Handler时需要手动创建,ActivityThread被创建时会初始化主线程的Looper,故主线程中默认可以使用Handler

ThreadLocal

ThreadLocal是线程内部数据存储类,通过它可以在指定线程中存储数据,且只能在该线程中访问,如在调用栈比较深时用于存储监听器,不需要全局变量即可实现监听线程的全部执行过程,且避免了监听器通过参数去传递

public class MainActivity extends AppCompatActivity {private static final String TAG = "MainActivity";private ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<>();protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mBooleanThreadLocal.set(true);Log.d(TAG, "onCreate: Thread#main mBooleanThreadLocal = " + mBooleanThreadLocal.get());new Thread("Thread#1"){@Overridepublic void run() {mBooleanThreadLocal.set(false);Log.d(TAG, "onCreate: Thread#1 mBooleanThreadLocal = " + mBooleanThreadLocal.get());}}.start();new Thread("Thread#2"){@Overridepublic void run() {Log.d(TAG, "onCreate: Thread#2 mBooleanThreadLocal = " + mBooleanThreadLocal.get());}}.start();}
}

打印如下,虽然不同线程使用同一个ThreadLocal对象,但值却不一样

Thread#main mBooleanThreadLocal = true
Thread#1 mBooleanThreadLocal = false
Thread#2 mBooleanThreadLocal = null

其set()方法如下,内部通过所传入线程threadLocals(ThreadLocalMap.Entry)存储数据

public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);
}ThreadLocalMap getMap(Thread t) {return t.threadLocals;
}void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {table = new Entry[INITIAL_CAPACITY];int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);table[i] = new Entry(firstKey, firstValue);size = 1;setThreshold(INITIAL_CAPACITY);
}static class Entry extends WeakReference<ThreadLocal<?>> {Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}
}

再看下set()的实际实现

private void set(ThreadLocal<?> key, Object value) {Entry[] tab = table;int len = tab.length;int i = key.threadLocalHashCode & (len-1);for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {ThreadLocal<?> k = e.get();if (k == key) {e.value = value;return;}if (k == null) {replaceStaleEntry(key, value, i);return;}}tab[i] = new Entry(key, value);int sz = ++size;if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();
}

其get()方法如下,若存在则返回数据,不存在则创建ThreadLocalMap,存储默认值null并返回

public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {T result = (T)e.value;return result;}}return setInitialValue();
}private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value;
}protected T initialValue() {return null;
}

MessageQueue

如下为插入操作enqueueMessage(),主要为链表的插入

boolean enqueueMessage(Message msg, long when) {......synchronized (this) {......msg.markInUse();msg.when = when;Message p = mMessages;boolean needWake;if (p == null || when == 0 || when < p.when) {msg.next = p;mMessages = msg;needWake = mBlocked;} else {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; prev.next = msg;}if (needWake) {nativeWake(mPtr);}}return true;
}

如下为读取操作,死循环遍历链表,在规定时间后将Message返回并从链表删除

Message next() {......for (; ; ) {......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;}} ......}......}
}

Looper

使用Handler需要Looper,否则会报错

在这里插入图片描述

可修改为如下

public class MainActivity extends AppCompatActivity {private static final String TAG = "MainActivity";protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);new Thread("Thread#1") {@Overridepublic void run() {Looper.prepare();Looper looper = Looper.myLooper();Handler handler = new Handler();Looper.loop();}}.start();}
}

prepare()创建Looper(内部创建MessageQueue),并存储到ThreadLocal

public static void prepare() {prepare(true);
}private static void prepare(boolean quitAllowed) {if (sThreadLocal.get() != null) {throw new RuntimeException("Only one Looper may be created per thread");}sThreadLocal.set(new Looper(quitAllowed));
}private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread();
}

loop()开启消息循环

  • 死循环一直从MessageQueue的next()方法获取Message
  • quit()、quitSafely()可让next()返回null退出Looper,前者直接退出,后者处理完MessageQueue中的已有消息后退出,退出后handler的send()返回false
  • 调用Handler的dispatchMessage(),回到创建Handler所在的Looper中执行
public static void loop() {final Looper me = myLooper();......final MessageQueue queue = me.mQueue;for (;;) {Message msg = queue.next();if (msg == null) {return;}......try {msg.target.dispatchMessage(msg);......}......}
}

Handler

sendMessage()将Message插入到MessageQueue

public final boolean sendMessage(@NonNull Message msg) {return sendMessageDelayed(msg, 0);
}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);
}

dispatchMessage()方法

  • msg.callback即是传进来的Runnable,若不为空则调用其run()
  • 判断mCallback是否为空,不为空调用其handleMessage(),当不想继承Handler创建子类时,其用于创建匿名内部类
  • 否则调用子类的handleMessage()
 public void dispatchMessage(@NonNull Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}}private static void handleCallback(Message message) {message.callback.run();}

runOnUiThread()

Activity中的runOnUiThread(),若当前线程是主线程,则直接执行,否则调用主线程创建的Handler中的post()

public final void runOnUiThread(Runnable action) {if (Thread.currentThread() != mUiThread) {mHandler.post(action);} else {action.run();}
}

相关文章:

Android中级——消息机制

消息机制 概念ThreadLocalMessageQueueLooperHandlerrunOnUiThread() 概念 MessageQueue&#xff1a;采用单链表的方法存储消息列表Looper&#xff1a;查询MessageQueue是否有新消息&#xff0c;有则处理&#xff0c;无则等待ThreadLocal&#xff1a;用于Handler获取当前线程的…...

【kubernetes】使用KubeSphere devops部署我的微服务系统

KubeSphere Devops 入门使用KubeSphere的Devops功能部署"我的微服务系统" &#xff08;内容学习于尚硅谷云原生课程&#xff09; kubesphere devops官方文档&#xff1a; https://v3-1.docs.kubesphere.io/zh/docs/devops-user-guide/how-to-use/create-a-pipeline-u…...

【哈士奇赠书活动 - 37期】- 〖深入浅出SSD:固态存储核心技术、原理与实战 第2版〗

文章目录 ⭐️ 赠书 - 《深入浅出SSD&#xff1a;固态存储核心技术、原理与实战 第2版》⭐️ 内容简介⭐️ 作者简介⭐️ 编辑推荐⭐️ 赠书活动 → 获奖名单 ⭐️ 赠书 - 《深入浅出SSD&#xff1a;固态存储核心技术、原理与实战 第2版》 ⭐️ 内容简介 本书从基础认知、核心技…...

25.CSS自定义形状按钮与悬停效果

效果 源码 <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>CSS Custom Shape Button</title><link rel="stylesheet" href="style.css"> </head> <body&…...

两条速度相差1350倍的sql语句

一起来学习研究下&#xff0c;看看是什么导致的这么大差别&#xff1a; SQL1: select * from RESOURCES where RES_STATUS 1 and PLATFORM_FLAG1 and RES_ID in (select RES_ID from DOWNLOADRECORDSwhere DOWNLOADRECORDS_TIME > DATE_SUB(now(),INTERVAL 7 DAY) grou…...

【UniApp开发小程序】小程序首页完善(滑到底部数据翻页、回到顶端、基于回溯算法的两列数据高宽比平衡)【后端基于若依管理系统开发】

文章目录 说明细节一&#xff1a;首页滑动到底部&#xff0c;需要查询下一页的商品界面预览页面实现 细节二&#xff1a;当页面滑动到下方&#xff0c;出现一个回到顶端的悬浮按钮细节三&#xff1a;商品分列说明优化前后效果对比使用回溯算法实现ControllerService回溯算法 优…...

使用errors.Wrapf()代替log.Error()

介绍不同语言的错误处理机制: Error handling patterns[1] Musings about error handling mechanisms in programming languages[2] 项目中 main调func1&#xff0c;func1调取func2... 这样就会出现很多的 if err ! nil { log.Printf()} , 在Kibana上查看时会搜到多条日志, 需要…...

企业面临的IP风险,如何应对?

IP风险画像为企业或组织在知识产权领域面临的潜在风险和威胁的综合概览。通过对相关知识产权的保护和管理&#xff0c;企业可以预测和应对潜在的法律、商业和声誉风险。 IP数据云帮助企业更好地了解和应对知识产权方面的风险。并提供了关于当前全球知识产权环境的重要信息&…...

L1-046 整除光棍(Python实现) 测试点全过

题目 这里所谓的“光棍”&#xff0c;并不是指单身汪啦~ 说的是全部由1组成的数字&#xff0c;比如1、11、111、1111等。传说任何一个光棍都能被一个不以5结尾的奇数整除。比如&#xff0c;111111就可以被13整除。 现在&#xff0c;你的程序要读入一个整数x&#xff0c;这个整…...

《Web安全基础》04. 文件上传漏洞

web 1&#xff1a;文件上传漏洞2&#xff1a;WAF 绕过2.1&#xff1a;数据溢出2.2&#xff1a;符号变异2.3&#xff1a;数据截断2.4&#xff1a;重复数据 本系列侧重方法论&#xff0c;各工具只是实现目标的载体。 命令与工具只做简单介绍&#xff0c;其使用另见《安全工具录》…...

文本匹配实战系列

引言 本系列文章开始介绍深度学习在文本匹配领域的应用&#xff0c;并且会尝试得到各种模型在给定的数据集上的表现。 深度文本匹配发展比较久&#xff0c;积累了很多文本匹配方法。也有很多的分类方式&#xff0c;一种分类方式是表示型和交互型。 表示型方法 表示型(repre…...

【Kafka】Kafka Stream简单使用

一、实时流式计算 1. 概念 一般流式计算会与批量计算相比较。在流式计算模型中&#xff0c;输入是持续的&#xff0c;可以认为在时间上是无界的&#xff0c;也就意味着&#xff0c;永远拿不到全量数据去做计算。同时&#xff0c;计算结果是持续输出的&#xff0c;也即计算结果…...

在Linux服务器上,查看系统最近的重启记录

在Linux服务器上&#xff0c;您可以查看系统的重启记录以了解系统何时进行了重启。系统的重启记录通常被记录在系统日志文件中。以下是在不同Linux发行版上查看系统重启记录的方法&#xff1a; 1. 使用 last 命令&#xff1a; 打开终端&#xff0c;并输入以下命令来查看系统的…...

Vue2023 面试归纳及复习

1. Vue 3中的Composition API&#xff08;Hooks&#xff09;是什么&#xff1f;它与Options API有何不同&#xff1f; 答&#xff1a;Composition API是Vue 3中引入的一种新的API风格&#xff0c; 用于组织和重用组件逻辑。它与Options API相比&#xff0c; 提供了更灵活和可…...

Android动态可编辑长度列表

概述 在界面实现一个列表&#xff0c;用户可以随意给列表新增或者删除项目&#xff0c;在开发中比较常用&#xff0c;但是真正做起来又有点花时间&#xff0c;今天花时间做一个&#xff0c;以便在以后的开发中用到。 详细 运行效果&#xff1a; 二、实现思路&#xff1a; 1…...

合并对象在 Typescript 中的实现与应用

合并对象在 Typescript 中的实现与应用 文章目录 合并对象在 Typescript 中的实现与应用一、简介二、实现1、函数实现2、参数说明3、返回值 三、使用示例四、实际应用场景五、拓展&#xff1a;使用 lodash-es 的 assign 函数进行对象合并1、简介2、安装与导入3、基础用法4、注意…...

antd upload组件beforeUpload返回promise之后,获取的文件不是file类型导致上传失败

之前的beforeUpload直接返回一个false值 &#xff0c;文件是可以正常与服务端进行传输的 beforeUpload: (file) > {return false},但是这样并不能阻止文件上传&#xff0c;看了官方文档后&#xff0c;改用返回promise对象上传 beforeUpload: (file) > {console.log(-befo…...

创建ffmpeg vs2019工程

0 写在前面 本文主要参考链接&#xff1a;https://www.cnblogs.com/suiyek/p/15669562.html 感谢作者的付出&#xff1b; 1 目录结构 2 下载yasm和nasm 如果自己在安装VS2019等IDE的时候已经安装了它们&#xff0c;则不用再单独进行安装&#xff0c;比如我这边已经安装了&a…...

无涯教程-机器学习 - Jupyter Notebook函数

Jupyter笔记本基本上为开发基于Python的数据科学应用程序提供了一个交互式计算环境。它们以前称为ipython笔记本。以下是Jupyter笔记本的一些功能,使其成为Python ML生态系统的最佳组件之一- Jupyter笔记本可以逐步排列代码,图像,文本,输出等内容,从而逐步说明分析过程。 它有…...

ubuntu安装单机的Consul

文章目录 场景解决启动方式 场景 公司使用Consul做注册发现中心以及管理配置&#xff0c;之前没有用过consul, 现在记录下ubuntu部署的过程 解决 apt 安装 wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-…...

AI Agent Harness Engineering 产品经理指南:如何定义智能体的“人设”与能力边界?

AI Agent Harness Engineering 产品经理指南:如何定义智能体的「人设」与能力边界 关键词:AI Agent、智能体管控工程(Harness Engineering)、产品经理、人设对齐、能力边界、智能体治理、生成式AI落地 摘要 随着生成式AI技术的成熟,AI Agent已经从概念验证阶段进入大规…...

告别内置ADC的烦恼:用ADS1119搞定STM32/DSP的高精度电压采样(附完整代码)

告别内置ADC的烦恼&#xff1a;用ADS1119搞定STM32/DSP的高精度电压采样&#xff08;附完整代码&#xff09; 在嵌入式系统开发中&#xff0c;电压采样是基础却至关重要的环节。许多工程师在使用STM32或DSP内置ADC时&#xff0c;常会遇到精度不足、抗干扰能力差、无法测量差分信…...

Biomni:生物医学图像分析从入门到精通,AI与传统CV融合实战

1. 项目概述&#xff1a;当AI学会“看”懂生物医学图像如果你在生物医学研究、药物发现或者临床诊断领域工作&#xff0c;大概率会和我一样&#xff0c;对海量的生物医学图像数据感到既兴奋又头疼。兴奋的是&#xff0c;这些图像——无论是显微镜下的细胞切片、组织病理学玻片&…...

DeepSeek LeetCode 2421. 好路径的数目 Python3实现

给你 Python3 版本的代码&#xff0c;思路和之前的 Java 实现一致&#xff1a; 完整代码 python class Solution: def numberOfGoodPaths(self, vals: List[int], edges: List[List[int]]) -> int: n len(vals) # 1. 构建邻接表 gr…...

MacOS光标增强工具:命令行驱动,实现自动化与个性化配置

1. 项目概述&#xff1a;当光标成为生产力工具如果你是一名长期在macOS上工作的开发者、设计师或者文字工作者&#xff0c;你肯定对系统自带的光标功能又爱又恨。爱的是它简洁流畅&#xff0c;恨的是它在某些高强度、多任务场景下显得力不从心。比如&#xff0c;当你需要在多个…...

从Awesome List到个人知识库:开发者如何高效筛选与组织技术资源

1. 项目概述&#xff1a;一份面向开发者的“Awesome List”清单 如果你在GitHub上混迹过一段时间&#xff0c;尤其是热衷于探索前沿技术、寻找优质学习资源或开源项目&#xff0c;那么你大概率见过或者使用过一种特殊的仓库—— Awesome List 。简单来说&#xff0c;这是一个…...

告别循环中的Thread.sleep():从IDEA告警到高效定时任务的最佳实践

1. 为什么Thread.sleep()在循环中是个危险信号&#xff1f; 第一次在IDEA里看到"Call to Thread.sleep() in a loop, probably busy-waiting"这个黄色警告时&#xff0c;我和大多数开发者一样不以为然——毕竟这个写法在教科书和早期项目中太常见了。直到有次我们的A…...

等保2.0合规实战:Redis安全配置核查与加固指南

1. Redis安全配置入门&#xff1a;为什么等保2.0要求这么严格&#xff1f; 我第一次接触Redis安全配置是在一次等保2.0合规检查中。当时客户系统因为Redis默认配置导致数据泄露&#xff0c;整个项目组连夜加班整改。从那以后&#xff0c;我就养成了每次部署Redis必做安全检查的…...

告别Keil!用Arduino生态玩转国产GD32芯片的3个实战技巧

用Arduino生态解锁GD32开发的三大高阶玩法 在嵌入式开发领域&#xff0c;Keil和IAR等传统工具链长期占据主导地位&#xff0c;但它们的封闭生态和复杂配置流程正在被更开放的解决方案挑战。GD32作为国产MCU的优秀代表&#xff0c;其与Arduino生态的融合为开发者提供了一条高效率…...

14502黄大年茶思屋145期难题 第二题 QLC盘多namespace并发备电量优化问题 标准化解题框架

总标题&#xff1a;黄大年茶思屋145期难题第二题 AI无偏差版脱敏题目标准化解题详细写作框架 子标题&#xff1a;QLC盘多namespace并发备电量优化问题 标准化解题框架 摘要 本文严格遵循AI无偏差脱敏标准化写作范式&#xff0c;完整复刻本期第二道脱敏原题全文&#xff0c;逐项…...