【Android】Handler用法及原理解析

文章目录

  • 用处
  • 基本用法
    • 用法一:使用sendMessage和handleMessage方法
    • 用法二:使用post方法
  • 法一工作原理
    • Handler的sendMessage
    • Message
      • 成员变量
    • MessageQueue
    • Looper
      • 主线程自动初始化
      • 子线程手动创建
      • prepare
      • loop
    • Handler的dispatchMessage
  • 法二工作原理
    • Handler的post
    • Handler的dispatchMessage
  • 总结

Handler 是用于在不同线程之间进行消息传递的机制。它与 LooperMessageQueue一起工作,帮助在不同线程间传递和处理消息,特别是在主线程与子线程之间进行通信。

用处

  1. 子线程通过 Handler 更新 UI
  2. 用于线程间通信

基本用法

用法一:使用sendMessage和handleMessage方法

  1. 创建Handler:通常在主线程中创建一个Handler,用于处理从其他线程发送的消息。

    Handler handler = new Handler();
    
  2. 发送消息:在子线程中,通常使用handler.sendMessage()来向主线程发送消息。

    new Thread(new Runnable() {@Overridepublic void run() {Message message = Message.obtain();message.what = 1;handler.sendMessage(message);}
    }).start();
    
  3. 处理消息:主线程中的Handler会调用handleMessage()方法来处理接收到的消息。

    Handler handler = new Handler(Looper.getMainLooper()) {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case 1:// 处理消息break;}}
    };
    

用法二:使用post方法

  1. 创建 Handler

    Handler handler = new Handler(Looper.getMainLooper());
    
  2. 使用 post 方法:可以直接在 Handler 中执行需要在主线程上运行的代码块。

    handler.post(new Runnable() {@Overridepublic void run() {// 执行主线程任务}
    });
    

    区别:

    sendMessage: 适用于更复杂的消息处理和任务调度,特别是需要在多个线程之间通信时。它依赖于 Handler 类,通过消息机制来传递和处理任务。

    post: 更加简洁和直接,主要用于在主线程中执行任务,尤其是需要更新 UI 的情况。适合用于在视图中直接执行代码而不涉及复杂的线程通信。

法一工作原理

主要依靠以下

  1. Looper:负责不断从 MessageQueue 中取出消息并分发给相应的 Handler 进行处理。

  2. HandlerHandler 是一个用于发送和处理消息的类。它可以用来将 MessageRunnable 对象发送到消息队列中,并在对应的线程中处理这些消息。

  3. Message:用于在 HandlerLooper 之间传递消息。

  4. MessageQueueMessageQueue 是消息的容器,负责存储和管理待处理的消息。维护了一个消息队列,按顺序处理消息。

举个例子:

天气预报:子线程获取到了天气数据,然后使用Message将数据包装,通过Handler对象的sendMessage方法将Message对象插入到消息队列(MessageQueue)中,主线程通过Looper.loop方法死循环检查MessageQueue中是否有消息,主线程的 Handler 会通过 handleMessage() 方法接收这个 Message

Handler的sendMessage

handler.sendMessage(message);

img

sendMessageDelayedsendEmptyMessageAtTimesendEmptyMessageDelayedsendEmptyMessage

这些Handler类中发送消息的方法最终调用的方法都是sendMessageAtTime

sendMessageAtTime方法中再调用enqueueMessage插入消息到消息队列

image-20240911201151006

Message

Message 类用于 HandlerLooper 之间的消息传递

Message类定义一个包含描述和任意数据对象的消息,该消息可以发送给Handler 。此对象包含两个额外的 int 字段和一个额外的对象字段,允许您在许多情况下不进行分配。
虽然 Message 的构造函数是公共的,但获取其中一个的最佳方法是调用Message.obtain()Handler.obtainMessage()方法之一,它们会从回收对象池中拉出它们。

成员变量

部分成员变量分析

// what 字段是一个用户自定义的消息代码,用来标识消息的类型。
public int what;//arg12 是一个整型值字段,用来传递简单的整型数据。
public int arg1;
public int arg2;// obj 是一个任意的对象,允许发送者将任何类型的对象传递给接收者。
public Object obj;// 可选的 Bundle,用于传递复杂数据。
Bundle data;//  target 是消息最终要传递到的 Handler,负责处理该消息。
Handler target;// 用于将 Message 对象串联在一起的指针,
Message next;

获取消息的首选方法是调用Message. obtain()

image-20240913093007364

最终实际都是通过obtain()方法获取的Message对象

obtain()源码:

// 从全局池返回一个新的 Message 实例。这允许我们在很多情况下避免分配新对象。
public static Message obtain() {synchronized (sPoolSync) {if (sPool != null) {Message m = sPool;sPool = m.next;m.next = null;m.flags = 0; // clear in-use flagsPoolSize--;return m;}}return new Message();
}

用于从全局池(sPool)中返回一个新的 Message 实例。如果池中有可用的消息对象,它将从池中获取,否则创建一个新的。可以优化性能

MessageQueue

MessageQueue 是一个用于保存消息(Message)的队列,其内部实现为一个单链表结构。

  1. enqueueMessage(Message msg, long when):用于将消息插入到队列的链表中,when 参数决定消息应该在什么时候被执行。消息按 when 的顺序排列,越早需要执行的消息排在前面。

  2. next():此方法用于从队列中取出下一个需要处理的消息。如果当前队列为空,线程会进入阻塞状态,直到新的消息到来。

Looper

用于运行线程消息循环的类。

Looper 是一种管理消息队列将消息分发到线程的机制。每个线程都有自己的消息队列和消息循环,这就是由 Looper 来实现的。

默认情况下,线程没有与之关联的消息循环;若要创建一个消息循环,请在要运行循环的线程中调用prepare ,然后loop以让其处理消息,直到循环停止。

主线程自动初始化

  • 主线程(UI线程)是应用启动时的默认线程,负责处理所有与用户界面相关的任务。Android 系统在应用启动时会自动为主线程创建一个 Looper,因此你无需手动调用 Looper.prepare()Looper.loop()
  • 这个主线程的 Looper 用来处理 UI 事件和系统消息,如用户点击、触摸事件等,这确保了 UI 的更新必须在主线程上执行

主线程ActivityThreadmain方法中自动初始化了:

public static void main(String[] args) {...// 为主线程准备消息循环,创建与当前线程关联的Looper,并设置为主线程的LooperLooper.prepareMainLooper();...// 初始化主线程的 Handler,用于处理消息队列中的消息if (sMainThreadHandler == null) {sMainThreadHandler = thread.getHandler();}...// 启动消息循环,开始处理消息队列中的消息,这一步将一直运行,直到应用程序终止Looper.loop();// 如果消息循环意外退出,抛出异常。正常情况下,主线程的消息循环不应该终止throw new RuntimeException("Main thread loop unexpectedly exited");
}

子线程手动创建

  • 当你在 子线程 中需要处理消息时(例如子线程需要执行长时间运行的任务,并且希望在任务完成后更新 UI),你需要手动为这个子线程创建 Looper。步骤通常包括:

    1. 调用 Looper.prepare():为当前线程创建一个 Looper 实例。
    2. 创建 Handler
    3. 调用 Looper.loop():进入消息循环,不断从消息队列中获取消息并分发给相应的 Handler 处理。

    示例

    class LooperThread extends Thread {public Handler mHandler;public void run() {Looper.prepare();mHandler = new Handler(Looper.myLooper()) {public void handleMessage(Message msg) {// process incoming messages here}};Looper.loop();}
    }
    

prepare

作用:

在当前线程中创建一个 Looper,以便线程可以拥有一个消息循环(MessageQueue)。通常在子线程中需要手动调用,主线程已经自动调用了 Looper.prepare()

由于Looper构造方法被私有了,只能通过调用 prepare() 方法

image-20240911213416670

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

这里的sThreadLocal是一个map类型容器,可以保证了1个线程中只有1个对应的Looper

1个线程中可以有多个Handler,1个线程中只有1个对应的Looper

如果已经存在Looper就抛出异常,否则new一个Looper放入sThreadLocal

image-20240911213829044

loop

  • Looper负责管理线程的消息循环,它在loop()方法中不断从MessageQueue中取出消息,并通过Handler的dispatchMessage()方法将消息分发给对应的Handler进行处理。
  public static void loop() {// 获取当前线程的looper对象final Looper me = myLooper();// 防止没有在该方法调用前调用prepare方法if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}...// 使用死循环来遍历消息队列,挑出需要执行的 Message 并分发for (;;) {if (!loopOnce(me, ident, thresholdOverride)) {return;}}} 
  • 在Looper的循环过程中,取出的每个Message对象都会通过HandlerdispatchMessage()方法传递给目标Handler,最终由目标Handler的handleMessage()方法处理消息。
private static boolean loopOnce(final Looper me,final long ident, final int thresholdOverride) {// 从消息队列中获取下一个消息。如果队列中没有消息,next() 可能会阻塞。// 当消息队列为空(即没有消息时),返回 null,表示消息队列正在退出。Message msg = me.mQueue.next(); if (msg == null) {// 如果消息为空,表示消息队列正在退出,终止循环。return false;}...try {// msg.target是指向一个Handler对象的引用,即消息的接收者// 分发消息到Handler进行处理,实际处理逻辑在msg.target.dispatchMessage()中。msg.target.dispatchMessage(msg);...} catch (Exception exception) {...} finally {...}...// 回收 Message 对象,避免对象的重复创建,提升性能。msg.recycleUnchecked();// 返回 true,表示循环可以继续处理下一个消息。return true;
}

Handler的dispatchMessage

没有使用post方法的话,调用重写的handleMessage方法

//  处理系统消息的方法。
public void dispatchMessage(@NonNull Message msg) {// 检查消息是否包含回调函数if (msg.callback != null) {// 如果有回调函数,处理回调handleCallback(msg);} else {// 如果没有回调函数// 检查是否设置了自定义的回调处理器if (mCallback != null) {// 如果自定义回调处理器处理了消息,直接返回if (mCallback.handleMessage(msg)) {return;}}// 如果自定义回调处理器没有处理消息,则调用默认的消息处理方法handleMessage(msg);}
}

法二工作原理

Handler的post

handler.post(new Runnable() {@Overridepublic void run() {}
});

post方法,一般以匿名内部类的形式发送Runnable对象,在Runnable对象重写的run()方法中直接对UI进行更新

image-20240913175006276


post类方法用以发送Runnable对象,send类方法用以发送Message对象,二者并不矛盾也不重复

观察Message类,可以发现安卓把Runnable对象作为Message的成员变量

image-20240913175515238


 sendMessageDelayed(getPostMessage(r), 0)

post方法又调用了sendMessageDelayed,注意getPostMessage(r),在这个方法里就把我们的Runnable对象赋值到了Runnable类型的callback上了

private static Message getPostMessage(Runnable r) {// 获取一个 Message 实例Message m = Message.obtain();// 将传入的 Runnable 对象设置为 Message 的回调函数。m.callback = r;// 返回配置好的 Message 对象。return m;
}

Handler的dispatchMessage

public void dispatchMessage(@NonNull Message msg) {if (msg.callback != null) {// 如果消息包含 callback,调用 handleCallback 方法handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return; // 自定义回调处理器处理了消息}}handleMessage(msg); // 默认处理消息}
}

发现该消息有Runnable就调用handleCallback方法执行 Runnable 对象。

    private static void handleCallback(Message message) {message.callback.run();}

其他部分与法一原理一致

总结

网图侵删



感谢您的阅读
如有错误烦请指正


参考:

  1. Handler类中发送消息-post()和postDelay()方法精炼详解
  2. android Handler机制原理解析(一篇就够,包你形象而深刻)_android handler的机制和原理-CSDN博客
  3. Handler 都没搞懂,拿什么去跳槽啊?0. 前言 做 Android 开发肯定离不开跟 Handler 打交道,它通 - 掘金 (juejin.cn)
  4. Android 消息机制(Handler + MessageQueue + Looper) - 知乎 (zhihu.com)
  5. Android——Handler一篇就看懂_android handler-CSDN博客

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

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

相关文章

electron多标签页模式更像客户端

Electron多标签页模式是指在Electron框架中实现的类似Web浏览器的多标签页功能。Electron是一个使用Web技术&#xff08;HTML、CSS和JavaScript&#xff09;来创建跨平台桌面应用程序的框架。在Electron中实现多标签页模式&#xff0c;通常需要借助一些特定的库或组件&#xff…

PMP--二模--解题--11-20

文章目录 14.敏捷--实践--每日站会--团队成员利用每日站会对彼此做出小的承诺&#xff0c;发现问题&#xff0c;并确保团队工作顺利进行。&#xff08;不是项目经理说&#xff0c;是团队成员&#xff09;11、 [单选] 在每日站会上&#xff0c;项目经理与团队成员逐个交流&#…

VMware ESXi 7.0U3q macOS Unlocker 集成驱动版更新 OEM BIOS 2.7 支持 Windows Server 2025

VMware ESXi 7.0U3q macOS Unlocker 集成驱动版更新 OEM BIOS 2.7 支持 Windows Server 2025 VMware ESXi 7.0U3q macOS Unlocker & OEM BIOS 2.7 集成网卡驱动和 NVMe 驱动 (集成驱动版) ESXi 7.0U3 标准版集成 Intel 网卡、Realtek USB 网卡 和 NVMe 驱动 请访问原文链…

口哨声、歌声、boing声和biotwang声:用AI识别鲸鱼叫声

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

web基础—dvwa靶场(八)XSS

XSS(DOM) 跨站点脚本&#xff08;XSS&#xff09;攻击是一种注入攻击&#xff0c;恶意脚本会被注入到可信的网站中。当攻击者使用 web 应用程序将恶意代码&#xff08;通常以浏览器端脚本的形式&#xff09;发送给其他最终用户时&#xff0c;就会发生 XSS 攻击。允许这些攻击成…

[Unity Demo]从零开始制作空洞骑士Hollow Knight第五集:再制作更多的敌人

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、制作敌人另个爬虫Crawler 1.公式化导入制作另个爬虫Crawler素材2.制作另个爬虫Crawler的Crawler.cs状态机3.制作敌人另个爬虫Crawler的playmaker状态机二、…

大型语言模型 (LLM) 劫持攻击不断升级,导致每天损失超过 100,000 美元

Sysdig 威胁研究团队 (TRT) 报告称&#xff0c;LLMjacking&#xff08;大型语言模型劫持&#xff09;事件急剧增加&#xff0c;攻击者通过窃取的云凭证非法访问大型语言模型 (LLM)。 这一趋势反映了 LLM 访问黑市的不断增长&#xff0c;攻击者的动机包括个人使用和规避禁令和制…

DNS服务

一.DNS介绍 DNS应用层协议 Domain Name System 域名系统 作用&#xff1a;实现域名解析&#xff0c;解析主机名所对应的IP地址&#xff0c; 在网络环境中设备与设备之间要想相互通信只能依赖IP地址&#xff0c;DNS服务器的作用是实现域名解析。 如上图所示&#xff0c;DNS存…

英飞凌 PSoC6 RT-Thread 评估板简介

概述 2023年&#xff0c;英飞凌&#xff08;Infineon&#xff09;联合 RT-Thread 发布了一款 PSoC™ 62 with CAPSENSE™ evaluation kit 开发板 &#xff08;以下简称 PSoC 6 RTT 开发板&#xff09;&#xff0c;该开发套件默认内置 RT-Thread 物联网操作系统。PSoC 6 RTT 开…

Matplotlib | 一文搞定Matplotlib从入门到实战演练!

文章目录 1 什么是Matplotlib1.1 Matplotlib的安装1.2 Matplotlib的基本使用 2 绘制直线3 绘制折线设置标签文字和线条粗细设置中文标题风格的设置 4 绘制曲线绘制曲线yx^2绘制正弦曲线和余弦曲线画布分区 5 绘制散点图绘制不同种类不同颜色的线 6 绘制条形图&#xff08;柱状&…

计算机网络 ---- OSI参考模型TCP/IP模型

目录 一、OSI参考模型 1.1 学习路线 1.2 OSI参考模型和TCP/IP模型 1.3 具体设备与具体层次对应关系 1.3.1 物理层 1.3.2 数据链路层 1.3.3 网络层 1.3.4 传输层 1.3.5 会话层、表示层、应用层 1.4 各层次数据传输单位 二、TCP/IP模型 2.1 学习路线 2.2 TCP/I…

对 JavaScript 原型的理解

笔者看了一些有关 JavaScript 原型的文章有感而发&#xff0c;就将所感所悟画了下来如果有理解错误和不足的地方&#xff0c;欢迎各位大佬指出&#xff0c;笔者感激不尽

【Django5】django的helloworld

安装django pip install djangoDjango官方中文文档 https://docs.djangoproject.com/zh-hans/5.1/Github链接 https://github.com/django/django创建Django项目 cd到想要创建项目的文件夹下&#xff0c;输入以下命令创建项目 这行代码将会在当前目录下创建一个 mysite 目录 …

9月26日云技术研讨会 | SOA整车EE架构开发流程及工具实施方案

面向服务的架构&#xff08;Service Oriented Architecture, SOA&#xff09;实施需要复杂的基础技术作为支撑&#xff0c;伴随着整车硬件资源的集中化、车载以太网等高速通信技术在车内的部署&#xff0c;将在未来一段时间内成为行业技术研究和市场布局的热点。 近年来&#x…

AD域控服务器

1.AD域控服务器安装 2.客户端Windows10加入域环境 3.组织单位OU和域用户创建 目的是分部门管理用户和使用域用户登录客户端 4.域用户安全策略 5.当客户端密码锁住了,管理员解锁账户。 6.只允许域用户使用自己的电脑 7.域策略 7.1统一客户端桌面壁纸 7.2重定向用户配置文件路径…

【macOS】【zsh报错】zsh: command not found: python

【macOS】【zsh Error】zsh: command not found: python 本地已经安装了Python&#xff0c;且能在Pycharm中编译Python程序并运行。 但是&#xff0c;在macOS终端&#xff0c;运行Python&#xff0c;报错。 首先要确认你在macOS系统下&#xff0c;是否安装了Python。 如果安…

每日刷题(算法)

我们N个真是太厉害了 思路&#xff1a; 我们先给数组排序&#xff0c;如果最小的元素不为1&#xff0c;那么肯定是吹牛的&#xff0c;我们拿一个变量记录前缀和&#xff0c;如果当前元素大于它前面所有元素的和1&#xff0c;那么sum1是不能到达的值。 代码&#xff1a; #def…

ElK 8 收集 Nginx 日志

1. 说明 elk 版本&#xff1a;8.15.0 2. 启个 nginx 有 nginx 可以直接使用。我这里是在之前环境下 docker-compose.yml 中启动了个 nginx&#xff1a; nginx:restart: alwaysimage: nginx:1.26.1ports:- "80:80"- "443:443"volumes:#- ./nginx/html:/…

Eigen3 教程基础篇(三)

参考 Eigen3 主页&#xff0c;Eigen3 官网教程 矩阵的本质&#xff0c;通过多种矩阵的应用去感受矩阵本质 3Blue1Brown 的线性代数&#xff0c;用可视化方法来表现线性代数的特性&#xff0c;强推 如何理解复数和虚数&#xff0c;有动画方便理解复数的意义 相关文章 Eigen…

《ElementUI/Plus 踩坑》el-table + sortablejs 拖拽顺序错乱(Vue2/3适用)

如图所示&#xff1a; 把第一行拖到最后一行&#xff0c;鼠标up&#xff1b;该行莫名其妙的跳到倒数第二行&#xff1b; 最后发现没有设置 el-table 属性 row-key &#xff0c;即行数据的 Key&#xff0c;用来优化 table 的渲染&#xff1b; 属性 row-key 描述如下&#xf…