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

news2024/12/22 2:54:14

首先明确, 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()) {
    @Override
    public 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 interception
    AndroidOs.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 certificates
    final 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(new
                LogPrinter(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的所属Handler
            throw 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 block
            if (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 block
            if (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.coloradmin.cn/o/2263547.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

windows openssl编译x64版libssl.lib,编译x64版本libcurl.lib,支持https,vs2015编译器

不要纠结&#xff0c;直接选择用perl编译&#xff01; 告诫想要用弄成vs编译版的&#xff0c;暂时先别给自己增加麻烦 告诫&#xff0c;以下执行的每一步&#xff0c;都不要纠结 先安装环境 nasm 64位版本 https://www.nasm.us/pub/nasm/releasebuilds/2.16.01/win64/nasm-…

汽车供应链 “剧变”开始,“智能感知潜在龙头”诞生

智能汽车产业链“剧变”已经开启&#xff0c;智能感知软硬件能力的权重正在不断被放大。 比如满足高阶泊车的第二代AK2超声波传感器、满足人机共驾场景需求的电子外后视镜&#xff08;CMS&#xff09;、iTOF 3D成像视觉感知&#xff08;用于舱内监控&#xff09;等新产品&…

Python+OpenCV系列:AI看图识人、识车、识万物

在人工智能风靡全球的今天&#xff0c;用 Python 和 OpenCV 结合机器学习实现物体识别&#xff0c;不仅是酷炫技能&#xff0c;更是掌握未来的敲门砖。本篇博文手把手教你如何通过摄像头或图片输入&#xff0c;识别人、动物、车辆及其他物品&#xff0c;让你的程序瞬间具备 AI …

JVM 详解

一. JVM 内存区域的划分 1. 程序计数器 程序计数器是JVM中一块比较小的空间, 它保存下一条要执行的指令的地址. [注]: 与CPU的程序计数器不同, 这里的下一条指令不是二进制的机器语言, 而是Java字节码. 2. 栈 保存方法中的局部变量, 方法的形参, 方法之间的调用关系. 栈又…

C# opencvsharp 流程化-脚本化-(2)ROI

ROI ROI也是经常需要使用的方法。特别是在图像编辑中。ROI又称感兴趣的区域&#xff0c;但是图像是矩阵是矩形的&#xff0c;感兴趣的是乱八七糟的&#xff0c;所以还有一个Mask需要了解一下的。 public class RoiStep : IImageProcessingStep{public ImageProcessingStepType…

wazuh-modules-sca-scan

sca模块主函数wm_sca_main -> wm_sca_start 检查policy文件中的每一个项目wm_sca_check_policy static int wm_sca_check_policy(const cJSON * const policy, const cJSON * const checks, OSHash *global_check_list) {if(!policy) {return 1;}const cJSON * const id c…

uniapp 自定义图标03

插入工程&#xff0c;修改名称文件内容 编译运行

在Windows本地用网页查看编辑服务器上的 jupyter notebook

​ Motivation: jupyter notebook 可以存中间变量&#xff0c;方便我调整代码&#xff0c;但是怎么用服务器的GPU并在网页上查看编辑呢&#xff1f; 参考 https://zhuanlan.zhihu.com/p/440080687 服务端(Ubuntu)&#xff1a; 激活环境 source activate my_env安装notebook …

【YOLO 项目实战】(11)YOLO8 数据集与模型训练

欢迎关注『youcans动手学模型』系列 本专栏内容和资源同步到 GitHub/youcans 【YOLO 项目实战】&#xff08;1&#xff09;YOLO5 环境配置与检测 【YOLO 项目实战】&#xff08;10&#xff09;YOLO8 环境配置与推理检测 【YOLO 项目实战】&#xff08;11&#xff09;YOLO8 数据…

Ubuntu22.04上安装esp-idf

一、安装准备# 建议使用Ubuntu 20.04 或 Ubuntu 22.04 操作系统 为了在 Ubuntu 22.04 中使用 esp-idf&#xff0c;需要安装一些依赖包 sudo apt-get install git wget flex bison gperf python3\python3-pip python3-venv cmake ninja-build ccache\libffi-dev libssl-dev dfu…

nginx-虚拟主机配置笔记

目录 nginx的安装可以查看nginx安装https://blog.csdn.net/m0_68472908/article/details/144609023?spm1001.2014.3001.5501 一、 基于域名 二、 基于IP 三、 基于端口 nginx的安装可以查看nginx安装https://blog.csdn.net/m0_68472908/article/details/144609023?spm100…

AlipayHK支付宝HK接入-商户收款(PHP)

一打开支付宝国际版 二、点开商户服务 三、下载源码

Soul Android端稳定性背后的那些事

前言&#xff1a;移动应用的稳定性对于用户体验和产品商业价值都有着至关重要的作用。应用崩溃会导致关键业务中断、用户留存率下降、品牌口碑变差、生命周期价值下降等影响&#xff0c;甚至会导致用户流失。因此&#xff0c;稳定性是APP质量构建体系中最基本和最关键的一环。当…

深度学习模型 DeepSeek-VL2 及其消费级显卡需求分析

DeepSeek-VL2 是由 DeepSeek 团队开发的一款先进的视觉语言模型&#xff0c;采用了混合专家&#xff08;MoE&#xff09;架构&#xff0c;旨在提升多模态理解能力。该模型包括三个版本&#xff1a;DeepSeek-VL2-Tiny、DeepSeek-VL2-Small 和 DeepSeek-VL2。每个版本具有不同的模…

首批|云轴科技ZStack成为开放智算产业联盟首批会员单位

近日 &#xff0c;在Linux基金会AI & Data及中国开源软件推进联盟的指导之下&#xff0c;开放智算产业联盟成立大会在北京成功召开。在大会上&#xff0c;联盟首次公布了组织架构并颁发了首批会员单位证书。凭借ZStack AIOS平台智塔和在智算领域的技术创新&#xff0c;云轴…

word实现两栏格式公式居中,编号右对齐

1、确定分栏的宽度 选定一段文字 点击分栏&#xff1a;如本文的宽度为22.08字符 2、将公式设置为 两端对齐&#xff0c;首行无缩进。 将光标放在 公式前面 点击 格式-->段落-->制表位 在“制表位位置”输入-->11.04字符&#xff08;22.08/211.04字符&#xff09;&…

go语言zero框架中config读取不到.env文件问题排查与解决方案

在Go语言中&#xff0c;如果你使用.env文件来存储环境变量&#xff0c;通常会用到一些第三方库&#xff0c;例如github.com/joho/godotenv&#xff0c;它可以帮助我们从.env文件中读取环境变量。然而&#xff0c;在使用godotenv时&#xff0c;可能会遇到一些问题&#xff0c;导…

修改vscode设置的原理

转载请标明出处&#xff1a;小帆的帆的专栏 修改vscode设置 首先需要理解的是&#xff0c;vscode的系统设置和插件设置都是通过settings.json文件管理的。 vscode中有三个Settings&#xff0c;三个Settings分别对应三个settings.json文件 Default Settings&#xff1a;默认…

Qt之修改窗口标题、图标以及自定义标题栏(九)

Qt开发 系列文章 - titles-icons-titlebars&#xff08;九&#xff09; 目录 前言 一、修改标题 二、添加图标 三、更换标题栏 1.效果演示 2.创建标题栏类 3.定义相关函数 4.使用标题栏类 总结 前言 在我们利用Qt设计软件时&#xff0c;经常需要修改窗口标题、更改软…

环境变量的知识

目录 1. 环境变量的概念 2. 命令行参数 2.1 2.2 创建 code.c 文件 2.3 对比 ./code 执行和直接 code 执行 2.4 怎么可以不带 ./ 2.4.1 把我们的二进制文件拷贝到 usr/bin 路径下&#xff0c;也不用带 ./ 了 2.4.2 把我们自己的路径添加到环境变量里 3. 认识PATH 3.…