智能座舱进阶-应用框架层-Handler分析

首先明确, handler是为了解决单进程内的线程之间的通信问题的。我也需要理解Android系统中进程和线程的概念, APP启动后,会有三四个线程启动起来,其中,有一条mainUITread的线程,专门用来处理UI事件,以及显示组件的生命周期,多个Activity的线程都是同一个。此外还要知晓,单进程内的多线程是共享内存、信号量、运行指令。如下图:
在这里插入图片描述

除了私有栈区,不能相互共享, 代码的方法指令、数据都可以在进程内任意线程执行。这里大家先记住这点, 在后面的问题3的解释中会用到。
enqueueMessage中的消息的存入,是可以设置时间先后的;
我们本篇幅文章只讨论三个问题:
1. Handler 的消息发送过程;
2. Message 被消耗的过程;
3. 为何需要Handler这个类的原因;

基础知识

跟handler相关的类:
Handler:发送消息的快递员(属于某个快递公司的职员)
Message:承担消息的包裹(可以放置很多东西的箱子)
MessageQueue:消息中心(这里会按照消息带的时间长短,一个排列好消息的单链表)
Looper: 真正在消费消息的永动机(这个是在主线程的,上面三个都在另一个线程里)

问题1#:Handler 的消息产生和发送过程

我们在Activity中的应用场景:
创建Handler的对象,里面也重写了真正去使用msg的回调方法:
//创建 Handler对象,并关联主线程消息队列

mHandler = new Handler(Looper.getMainLooper()) {@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);···略···}}
};

发送消息:

Message msg = new Message();
msg.setData(bundle);
msg.what = 2;
mHandler.sendMessage(msg)

消息的产生, 可以在任何地方通过Message 去创建,它是一个比较简单的实体类, 包含1、what,when,target等。 这里面说一下:
2、what:就是携带的数据,Int类型;
3、when:这个一般不怎么用, 但是是可以使用,具体是输入时间长短后,当在msg发送到MessageQueue的队列里, 会根据when和当前时间来推送,需要去消费它的具体时间, 跟队列里的消息进行对比,然后排列插进去;
4、target: 这个一般我们没有赋值,是因为他在Messag的实例化对象的时候【obtain()方法】或者是在message的后续传递中底层会帮我们赋值, 这个在最后looper里消费的时候, 会使用,用它找到这个消息是用哪个handler来消费,调用他的handleMessage()方法。 这里也就说明了, 一个looper是支持绑定多个hander对象的,同时也不会混乱。

我们现在看一下handler的实例化过程:

public Handler(Callback callback, boolean async) {//......mLooper = Looper.myLooper();  //返回与当前线程关联的Looper对象,在后面Looper会讲到if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread " + Thread.currentThread()+ " that has not called Looper.prepare()");}mQueue = mLooper.mQueue;  //返回Looper对象的消息队列,在后面MessageQueue会讲到mCallback = callback;  //接口回调mAsynchronous = async; //是否异步}public interface Callback {public boolean handleMessage(Message msg); //这个函数大家都很熟悉了,暂不细说,总之都知道是用来回调消息的}

整个构造方法的过程中会确立以下几件事:

• 获取当前Handler实例所在线程的Looper对象:mLooper = Looper.myLooper(),这个looper获取的就是主线程那个loop
• 如果Looper不为空,则获取Looper的消息队列,赋值给Handler的成员变量mQueue:mQueue = mLooper.mQueue
• 可以设置Callback 来处理消息回调:mCallback = callback。

looper这里面为什么没有创建直接获取呢, 因为我们的Activity在每次创建的时候,
ActivityThread的1个静态的main();
而main()内则会调用Looper.prepareMainLooper()为主线程生成1个Looper对象,同时也会生成其对应的MessageQueue对象。为进程中的管理进行初始化。
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, “ActivityThreadMain”);

    // Install selective syscall interceptionAndroidOs.install();// CloseGuard defaults to true and can be quite spammy.  We// disable it here, but selectively enable it later (via// StrictMode) on debug builds, but using DropBox, not logs.CloseGuard.setEnabled(false);Environment.initForCurrentUser();// Make sure TrustedCertificateStore looks in the right place for CA certificatesfinal File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());TrustedCertificateStore.setDefaultUserDirectory(configDir);// Call per-process mainline module initialization.initializeMainlineModules();Process.setArgV0("<pre-initialized>");Looper.prepareMainLooper();  // 在这里创建新的looper

ActivityThread thread = new ActivityThread();
thread.attach(false);

    if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();// 获取Handler}if (false) {Looper.myLooper().setMessageLogging(newLogPrinter(Log.DEBUG, "ActivityThread"));}// End of event ActivityThreadMain.Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);Looper.loop();// 开始调用loop()方法

好了,言归正传, handler创建完成调用

mHandler.sendMessage(msg)方法进行发送:

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue; //获得当前的消息队列
if (queue == null) { //若是在创建Handler时没有指定Looper,就不会有对应的消息队列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(MessageQueue queue, Message msg, long uptimeMillis) {msg.target = this;   //这个target就是前面我们说到过的if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);
}

消息通过这个方法 queue.enqueueMessage(msg, uptimeMillis)进入到了queue队列里了。

boolean enqueueMessage(Message msg, long when) {if (msg.target == null) {  //判断msg的所属Handlerthrow new IllegalArgumentException("Message must have a target.");}//......synchronized (this) {  //因为是队列,有先后之分,所以用了同步机制//......msg.when = when;Message p = mMessages;  //对列中排在最后的那个Message //......if (p == null || when == 0 || when < p.when) {    //若队列为空,或者等待时间为0,或者比前面那位的等待时间要短,就插队msg.next = p;  //此处的next就是前面我们在Message提到的,指向队列的下一个结点mMessages = msg;//......} else { //......Message prev;for (;;) {     //此处for循环是为了取出一个空的或者when比当前Message长的一个消息,然后进行插入prev = p;p = p.next;if (p == null || when < p.when) {break;}//......}msg.next = p;       // 置换插入prev.next = msg;  // 置换插入}//......}return true;}

这个就消息新建产生,然后发送到了queue的消息队列里,对待looper去取然后分发消费了。

问题2#:Message的消费过程

这个我们要看一下looper.loop()方法:

public static void loop() {final Looper me = myLooper();if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}final MessageQueue queue = me.mQueue;Binder.clearCallingIdentity();final long ident = Binder.clearCallingIdentity();for (;;) {// 无限循环Message msg = queue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return;}......final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();final long end;try {// 处理消息msg.target.dispatchMessage(msg);......}

前面有代码片里说到 ActivityThread.mian-> Looper.prepareMainLooper()->mLooper.loop():
我们只要看Loop()方法就可以了:

public static void loop() {final Looper me = myLooper();if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}final MessageQueue queue = me.mQueue;Binder.clearCallingIdentity();final long ident = Binder.clearCallingIdentity();for (;;) {// 无限循环Message msg = queue.next(); // might blockif (msg == null) {// No message indicates that the message queue is quitting.return;}......final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();final long end;try {// 处理消息msg.target.dispatchMessage(msg);......

这里面就看到,两件事情,根据target来判断是哪个handler,同时调用它的dispatchMessage来分发消息:

 public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}}

最终调用了 我们附带了的callback的handleMessage()方法。

这里面还有一个疑惑, looper是怎么能够一直处理消息的。 大家看loop()方法中, 有一个for(;;)死循环,这个其实就是永远在轮询队列中的消息,for循环是可以被阻塞的, 在这里就是这个函数Message msg = queue.next(); 就是如果队列没有消息了,它就会阻塞, 类似thread.sleep(1000)的方法。 当有新消息, queue.next()就会正常,继续执行消息。

问题3#: 为什么需要handler

需要handler,是因为在Android的设计里,不允许除了UI主线程外的其他线程对UI进行操作,最终都会走到View.requestLayout()里面判断当前线程是否是主线。
所以我们就需要想办法,需要子线程执行的某个阶段,有需求更新UI,那我们就必须通知在主线程里面去执行刷新UI的代码,这个在很多地方都有这种设计, 设计一个轮转片的循环,线程内的通信,通过消息来通知。 比如JS这种单线程设计语言, 就有Emit的设计。
1.创建了一个Looper对象保存在ThreadLocal中。这个Looper同时持有一个MessageQueue对象。
2.创建Handler获取到Looper对象和MessageQueue对象。在调用sendMessage方法的时候在不同的线程(子线程)中把消息插入MessageQueue队列。

3.在主线程中(UI线程),调用Looper的loop()方法无限循环查询MessageQueue队列是否有消息保存了。有消息就取出来调用dispatchMessage()方法处理。这个方法最终调用了我们自己重写了消息处理方法handleMessage(msg);这样就完成消息从子线程到主线程的无声切换。

最后补充:我们经常使用Handler没有创建Looper调用Looper.pepare()和Looper.loop()是因为在ActivityThread中已经创建了主线程的Looper对象,保存在了ThreadLocal中,我们在创建Hander的时候就会从ThreadLocal中取出来这个Looper。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/493805.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

构建高性能异步任务引擎:FastAPI + Celery + Redis

在现代应用开发中&#xff0c;异步任务处理是一个常见的需求。无论是数据处理、图像生成&#xff0c;还是复杂的计算任务&#xff0c;异步执行都能显著提升系统的响应速度和吞吐量。今天&#xff0c;我们将通过一个实际项目&#xff0c;探索如何使用 FastAPI、Celery 和 Redis …

go面试问题

1 Go的内存逃逸如何分析 go build -gcflags-m main_pointer.go 2 http状态码 300 请求的资源可包括多个位置&#xff0c;相应可返回一个资源特征与地址的列表用于用户终端&#xff08;例如&#xff1a;浏览器&#xff09;选择 301 永久移动。请求的资源已被永久的移动到新U…

JVM性能优化一:初识内存泄露-内存溢出-垃圾回收

本文主要是让你充分的认识到什么叫做内存泄露&#xff0c;什么叫做内存溢出&#xff0c;别再傻傻分不清了&#xff0c;别再动不动的升级服务器的内存了。 文章目录 1.基本概念1.1.内存泄露1.2.内存溢出1.3.垃圾回收1.4.内存泄露-垃圾回收-内存溢出三者的关系关系 2.代码示例2.…

安装milvus以及向量库增删改操作

首先电脑已经安装了docker windows电脑可下载yml文件 https://github.com/milvus-io/milvus/releases/download/v2.4.6/milvus-standalone-docker-compose.yml 创建milvus文件夹&#xff0c;并在这个目录下创建五个文件夹&#xff1a;conf、db、logs、pic、volumes、wal 然后…

ARP..

ARP 0 前言 真正接触到现网才发现ARP十分重要&#xff0c;无论是排错还是S-MLAG都需要用到ARP这个协议&#xff0c;以前对于ARP的理解比较混乱&#xff1b;所以这次对其中的主要内容做个梳理&#xff1b;一定要学好ARP&#xff01;&#xff01;&#xff01; 1 ARP的概念 Ar…

单片机上电后程序不运行怎么排查问题?

1.电源检查。使用电压表测量单片机的电源电压是否正常&#xff0c;确保电压在规定的范围内&#xff0c;如常见的5V。 2.复位检查。检查复位引脚的电压是否正常&#xff0c;在单片机接通电源时&#xff0c;复位引脚通常会有一个高电平&#xff0c;按下复位按钮时&#xff0c;复位…

SKETCHPAD——允许语言模型生成中间草图,在几何、函数、图算法和游戏策略等所有数学任务中持续提高基础模型的性能

概述 论文地址&#xff1a;https://arxiv.org/pdf/2406.09403 素描是一种应用广泛的有效工具&#xff0c;包括产生创意和解决问题。由于素描能直接传达无法用语言表达的视觉和空间信息&#xff0c;因此从古代岩画到现代建筑图纸&#xff0c;素描在世界各地被用于各种用途。儿童…

Linux应用开发————mysql数据库表

mysql数据库表操作 查看表的结构 mysql> desc / describe 表名; 或者&#xff1a; mysql> show create table 表名; 常见数据库引擎&#xff1a; innodb, myISAM... 删除表 mysql> drop tabl…

【C#】实现Json转Lua (Json2Lua)

关键词: C#、JsonToLua、Json2Lua、对象序列化Lua 前提需引入NewtonsofJson&#xff0c;引入方法可先在Visual Studio 2019 将Newtonsoft.Json.dll文件导入Unity的Plugins下。 Json格式字符串转Lua格式字符串&#xff0c;效果如下&#xff1a; json字符串 {"1": &q…

2.4 网络概念(分层、TCP)

网络层与传输层概述 网络层&#xff1a; 抽象概念&#xff1a;网络层是基于 IP 的抽象概念&#xff0c;与数据链路层用 MAC 地址标记设备不同。MAC 地址是一种具体化的概念&#xff0c;绑定于所在的物理网络&#xff0c;而 IP 地址可以是固定的&#xff0c;也可以通过路由动态…

LabVIEW伸缩臂参数监控系统

LabVIEW开发伸缩臂越野叉车参数监控系统主要应用于工程机械中的越野叉车&#xff0c;以提高车辆的作业效率和故障诊断能力。系统通过PEAK CAN硬件接口和LabVIEW软件平台实现对叉车作业参数的实时监控和故障分析&#xff0c;具有良好的实用性和推广价值。 系统组成 系统主要由P…

多目标优化常用方法:pareto最优解

生产实际中的许多优化问题大都是多目标问题&#xff0c;举个例子&#xff1a;我们想换一份工资高、压力小、离家近的新工作&#xff0c;这里工资高、压力小、离家近就是我们的目标&#xff0c;显然这是一个多目标问题&#xff0c;那我们肯定想找到这三个目标同时最优的工作&…

跨站脚本攻击的多种方式——以XSS-Labs为例二十关详解解题思路

一、XSS-Labs靶场环境搭建 1.1、XSS介绍 跨站脚本攻击&#xff08;XSS&#xff09;_跨站脚本测试-CSDN博客https://coffeemilk.blog.csdn.net/article/details/142266454 1.2、XSS-Labs XSS-Labs是一个学习XSS攻击手法的靶场&#xff0c;方便我们系统性的学习掌握跨站脚本攻击…

【win10+RAGFlow+Ollama】搭建本地大模型助手(教程+源码)

一、RAGFlow简介 RAGFlow是一个基于对文档深入理解的开源RAG&#xff08;Retrieval-augmented Generation&#xff0c;检索增强生成&#xff09;引擎。 主要作用&#xff1a; 让用户创建自有知识库&#xff0c;根据设定的参数对知识库中的文件进行切块处理&#xff0c;用户向大…

54、库卡机器人轴的软限位设置

步骤1&#xff1a;将用户组改为“专家”。 步骤2&#xff1a;点击“投入运行”----“售后服务”-----“软件限位开关” 步骤3&#xff1a;就可以针对每个轴修改对应的角度值&#xff0c;然后点击“保存”。

0基础学前端-----CSS DAY9

0基础学前端-----CSS DAY9 视频参考&#xff1a;B站Pink老师 今天是CSS学习的第九天&#xff0c;今天开始的笔记对应Pink老师课程中的CSS第四天的内容。 本节重点&#xff1a;常见网页布局以及清除浮动 2. 常见网页布局 2.1 常见网页布局 有以下三种&#xff1a; 参考代码…

【自动化】Python SeleniumUtil 工具 开启开发者模式 自动安装油猴用户脚本等

【自动化】Python SeleniumUtil 工具 【Python】使用Selenium 操作浏览器 自动化测试 记录-CSDN博客文章浏览阅读58次。文章浏览阅读42次。【附件】Selenium chromedriver 驱动及浏览器下载。【附件】Selenium chromedriver 驱动及浏览器下载-CSDN博客。3.安装Chrome浏览器驱动…

UE5 移植Editor或Developer模块到Runtime

要将源码中的非运行时模块移植到Runtime下使用&#xff0c;个人理解就是一个解决编译报错的过程&#xff0c;先将目标模块复制到项目的source目录内&#xff0c;然后修改模块文件夹名称&#xff0c;修改模块.build.cs与文件夹名称保持一致 修改build.cs内的类名 &#xff0c;每…

全志H618 Android12修改doucmentsui选中图片资源详情信息

背景: 由于当前的文件管理器在我们的产品定义当中,某些界面有改动的需求,所以需要在Android12 rom中进行定制以符合当前产品定义。 需求: 进入file文件管理器后,点击选中图片资源,选中功能按钮,获取信息,不显示“调试信息(仅开发者)”;现状是,获取信息,显示“调试信…

【WPS安装】WPS编译错误总结:WPS编译失败+仅编译成功ungrib等

WPS编译错误总结&#xff1a;WPS编译失败仅编译成功ungrib等 WPS编译过程问题1&#xff1a;WPS编译失败错误1&#xff1a;gfortran: error: unrecognized command-line option ‘-convert’; did you mean ‘-fconvert’?解决方案 问题2&#xff1a;WPS编译三个exe文件只出现u…