一文读懂 Handler 消息处理机制(源码实战)

news/2024/5/19 11:46:02/文章来源:https://blog.csdn.net/m0_62167422/article/details/127090991

Android 异步消息处理机制解析

Android 中的异步消息处理主要由四个部分组成,Message、Handler、MessageQueue、Looper

但是当我们提到 Android 异步处理机制的时候,我们首先会想到 Handler,而大多数Android 初学者对于 Handler作用局限子线程如何更新 UI 这一方面;其实 Handler 能做的事情远不止于此,它贯穿于 Android 应用的整个生命周期,如果没有 Handler,我们编写的应用就无法正常运行;总的来说,它的作用体现在两大方面:

  • 处理延时任务(告诉app在将来的某个时间执行某个任务)
  • 线程之间通信(子线程更新UI就是其中的一个应用)

在程序中怎么让 Handler 识别出自己发送的消息呢?针对这个问题我们可以想到,在发送的时候给消息做一个标记,标记出发送消息的 Handler,那么在处理消息的时候就可以根据这个标记来找到应该要处理的 Handler 对象了

异步消息处理的流程

1、在主线程中创建一个 Handler 对象,并重写 handleMessage 方法,我们主要在 handleMessage 中进行一系列的操作
2、当子线程中需要进行 UI 更新时,就在子线程中创建一个 Message 对象,并通过 Handler 将这条消息发送出去
3、经 Handler 发送的消息会被添加到 MessageQueue 中等待被处理,而 Looper 会一直尝试从 MessageQueue 中取出消息进行处理
4、最后 Looper 会将消息发送到 Handler 的 handleMessage() 方法中处理

源码实战

Message

首先来看一下 Message 类中包含的一些重要字段:

	//用来识别消息的字段public int what;//Message中可以携带的整形数据arg1和arg2public int arg1;public int arg1;//Message中可携带的对象public Object obj;//记录是哪个Handler发送的消息Handler target;//指针,指向下一个MessageMessage next;//缓存池private static Message sPool;//当前缓存池的大小private static int sPoolSize = 0;//缓存池最大容量private static final int MAX_POOL_SIZE = 50;

接下来看一下它的构造方法:

	public Message() {}

由源码中看到构造方法中什么都没有,然而在开发中并不建议通过 new 的方式创建出 Message 对象;Message 中由一个 static 类型的 sPool 字段,它代表了缓存池

也就是说当一个消息被处理掉之后并不会直接销毁,而是放在缓存池中,下次再需要时可以通过 Message 提供的 obtain 方法从池中得到,这样一来便减少了内存的开销,实现了资源的复用

obtain 方法有很多重载,这里只分析无参的重载,其他方法的原理是类似的:

	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();}

这个方法很简单,如果池子不为null则会取出池中第一个 Message(头节点)并将 sPoolSize 减一,否则返回一个 new 出来的 Message 对象

准备工作

在发送消息之前,要进行一些准备工作(初始化操作);这一点很好理解:想坐飞机要通过安检,因此首先要有一个安检机,所以在发消息之前要确保有一个 Looper 对象(MessageQueue 在 Looper 中创建);我们来看一下 Looper 的构造函数:

	private Looper(boolean quitAllowed) {//创建出消息队列mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread();}

发送消息

发送消息是通过 Handler 提供的一系列 send 方法完成的,那么首先要有 Handler 对象才能发送,所以我们先来看一下 Handler 的构造方法:

	public Handler(Callback callback, boolean async) {if (FIND_POTENTIAL_LEAKS) {final Class<? extends Handler> klass = getClass();if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&(klass.getModifiers() & Modifier.STATIC) == 0) {Log.w(TAG, "The following Handler class should be static or leaks might occur: " +klass.getCanonicalName());}}mLooper = Looper.myLooper();if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()");}mQueue = mLooper.mQueue;mCallback = callback;mAsynchronous = async;}

有了 Handler 对象之后,就可以通过它提供的一系列 send、post 方法来发送消息了;不管用哪一个方法来发送消息,最终都会调用 Hander的 enqueueMessage 方法

Handle r的 enqueueMessage 方法如下所示:

	private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {msg.target = this;if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);}

可以看到不管是哪个方法,最终都会调用 MessageQueue 的 enqueueMessage 方法:

	boolean enqueueMessage(Message msg, long when) {if (msg.target == null) {//如果目标为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;//如果队列位null或者message的when=0或者message的when小于队列第一个元素的whenif (p == null || when == 0 || when < p.when) {//此时应该将新消息插入到链表首部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;//找到p和prev,其中p为message的下一个元素,prev为message的上一个元素for (;;) {prev = p;p = p.next;if (p == null || when < p.when) {break;}if (needWake && p.isAsynchronous()) {needWake = false;}}//将message插入到链表的对应位置msg.next = p; // invariant: p == prev.nextprev.next = msg;}// We can assume mPtr != 0 because mQuitting is false.if (needWake) {nativeWake(mPtr);}}return true;}

总的来说,一个线程中可以创建许多 Handler,然而这些 Handler 对象持有的 Looer、MessageQueue 引用指向的都是相同的对象(线程中唯一的的);这样一来,在其他线程中用这些 Handler 对象去发送消息(发出的消息持有发消息的 Handler 对象的引用),发出去的消息最终都是被放到了创建 Handler 线程中对应那个 MessageQueue 中

而创建 Handler 的线程中通过 Looper.loop 死循环不断地从消息队列中取出消息,这样便实现了线程之间的通信由于对消息的入队和出队操作都是加了锁的,因此便保证了通信的安全性

上边分析了那么多, 看了那么一大堆的代码,我们清楚的了解到了 Handler 机制对于 Android 系统的重要性;所以也有人说 Handler 消息机制是 Framework 层的发动机,这么考虑一下一点也不为过吧;有需要了解更多关于Android Framework 消息机制 相关资讯的朋友;可点击此处查看直达方式 或者私信发送 “进阶” ,即可获取一份 Android Framework 源码解析思维导图及学习手册,以便大家能够更好的学习 Android Framework

Android Framework 源码解析思维导图

Android Framework 学习手册

好了,以上就是今天要分享的内容,大家觉得有用的话,可以点赞分享一下;如果文章中有什么问题欢迎大家指正;欢迎在评论区或后台讨论哈~

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

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

相关文章

医院患者挂号app(IDEA,SpringBoot,SSM,MySQL)+全套视频教程

【项目功能介绍】 本系统包含后台管理和前端app双端系统&#xff0c;后台管理的功能包含: 登录, 退出, 修改管理员信息(基本信息与头像),资源管理, 角色管理&#xff0c;资源权限分配, 数据字典管理&#xff0c;用户管理,医院管理, 医生管理; app端功能包含医生与患者二种角色…

CNN中添加HOG特征的pytorch实现——基于Alexnet

CNN中添加HOG特征的pytorch实现——基于Alexnet 几天前花了差不多两天时间基本实现了这个需求&#xff0c;经历了从一开始的毫无头绪&#xff0c;到最后对CNN模型 (加载数据集和数据流向) 和HOG特征有了更进一步的理解&#xff0c;实现需求之后又杂七杂八的看了一些相关的文章…

DolphinScheduler 进阶(资源中心)

文章目录内置参数引用依赖资源内置参数 DolphinScheduler 提供了一些时间相关的系统参数&#xff0c;方便定时调度使用。 1&#xff09;基础内置参数 变量名参数说明system.biz.date${system.biz.date}定时时间前一天&#xff0c;格式为 yyyyMMddsystem.biz.curdate${system…

资深腾讯架构师耗时2个月整理的Redis全套学习笔记,涵盖所有核心知识点

Redis 是一个开源、基于内存、使用 C 语言编写的 key-value 数据库&#xff0c;并提供了多种语言的 API。它的数据结构十分丰富&#xff0c;基础数据类型包括&#xff1a;string&#xff08;字符串&#xff09;、list&#xff08;列表&#xff0c;双向链表&#xff09;、hash&a…

18【命令设计模式】

文章目录十八、命令设计模式18.1 命令设计模式简介18.1.1 命令设计模式概述18.1.2 命令设计模式的UML类图18.2 命令设计模式的实现18.3 命令设计模式的优缺点十八、命令设计模式 18.1 命令设计模式简介 18.1.1 命令设计模式概述 命令设计模式&#xff08;Command Pattern&am…

元宇宙人均月入过万?智联报告大揭秘(附下载);华盛顿大学·线性代数进阶课程;电子书·深度神经网络应用(Keras);前沿论文 | ShowMeAI资讯日报

&#x1f440;日报合辑 | &#x1f4c6;电子月刊 | &#x1f514;公众号下载资料 | &#x1f369;韩信子 &#x1f4e2; 人均『月入过万』&#xff1f;元宇宙领域就业现状究竟如何 自2021年元宇宙元年之后&#xff0c;大批元宇宙相关岗位如雨后春笋涌现在市场上。从耳熟能详的…

罗克韦尔 Rockwell RSLogix 500 Crack

罗克韦尔自动化除了提供自动化基础硬件外&#xff0c;还提供大量的软件工具&#xff0c;并在其产品中包含了最新的Microsoft的32位Windows NT及ActiveXTM技术。这些软件为用户采用开放式的工业标准提供方便。另外借助DDE、ActiveX和ODBC兼容技术等&#xff0c;使现场数据能容易…

Chrome 浏览器的 ChromeOptions 参数介绍,selenium 动态渲染必看

背景 在使用 selenium 浏览器渲染技术&#xff0c;爬取网站信息时&#xff0c;默认情况下就是一个普通的纯净的chrome浏览器&#xff0c;而我们平时在使用浏览器时&#xff0c;经常就添加一些插件&#xff0c;扩展&#xff0c;代理之类的应用。相对应的&#xff0c;当我们用ch…

SpringBoot+SpringSecurity+JWT

声明&#xff1a; 该文章为b站博主“三更草堂”视频讲解文档。 视频链接&#xff1a; https://www.bilibili.com/video/BV1mm4y1X7Hc/?spm_id_from333.1007 文章目录SpringSecurity从入门到精通课程介绍0. 简介1. 快速入门1.1 准备工作1.2 引入SpringSecurity2. 认证2.1 登陆校…

Windows系统深度学习Anaconda、PyTorch软件安装教程

1.作者介绍 陈梦丹&#xff0c;女&#xff0c;西安工程大学电子信息学院&#xff0c;2022级研究生&#xff0c;张宏伟人工智能课题组 研究方向&#xff1a;机器视觉与人工智能 电子邮件&#xff1a;1169738496qq.com 安装思路&#xff1a; 根据电脑显卡选择cuda版本&#xf…

软件供应链中代码签名的重要性

你怎麼知道你的代碼是可信的&#xff1f; 在一個難以獲得信任的世界裡&#xff0c;這是一個重要的問題要問自己。 我們如何知道我們正在運行的應用程序、我們正在部署的容器或我們交付給客戶的代碼是真實的&#xff1f; 我們怎麼知道它沒有被篡改&#xff1f; 這一切都歸結為…

VUE3-实习笔记02

本节&#xff1a;ant design 3 的自定义校验规则 这里是获取数据&#xff0c;保存数据&#xff0c;有两个接口&#xff0c;测试邮箱的校验是非必要的&#xff0c;点击“发送测试邮件”才校验&#xff0c;所以规则是要另外加进去的&#xff0c;然后点击保存的时候&#xff0c;把…

python基本操作的作业

查看变量类型的Python内置函数是____。type () 返回单字符在unicode编码表中的值的函数为____。ord() Python中的布尔型只有两个值&#xff0c;分别为&#xff1a;____和____。True 和 False 执行下列语句后&#xff0c;从键盘输入10&#xff0c;程序的结果是&#xff1a; 1…

ActiveReportsJS 在 React 应用程序中添加和自定义查看器

谷歌搜破解版&#xff1a;ActiveReportsJS 3.2中文版是一个客户端报告解决方案&#xff0c;可用于各种不同的前端框架和库。从 vanilla JavaScript 到 NuxtJS&#xff0c;ARJS 在设计应用程序和使用其组件时非常灵活。在本文中&#xff0c;我们将讨论如何通过 Visual Studio Co…

Delphi 开发过程中简单的版本管理与回退

前言 很多时候&#xff0c;我们在进行代码编写或者窗体设计的时候&#xff0c;如果需要回退某些早前修改的内容&#xff0c;基本是使用CtrlZ做刚刚修改内容的回退处理&#xff0c;但如果某些修改已经过了一段时间我们就不能再这样退回&#xff0c;并且CtrlZ只能按顺序一个一个回…

用QT的modbus相关类编写ModbusTcp主站

目标程序运行界面&#xff0c; 1.新建Qwiget工程&#xff0c;工程文件如下&#xff0c; #------------------------------------------------- # # Project created by QtCreator 2022-09-28T14:53:02 # #------------------------------------------------- QT co…

MYSQL介绍——数据库查询

条件查询 很多时候&#xff0c;我们使用数据库时并不是关心表里所有的数据&#xff0c;而是一部分满足条件的数据&#xff0c;这类条件要用WHERE子 句来实现数据的筛选。 SELECT …… FROM …… WHERE 条件 [ AND | OR ] 条件 …… ;下面给出一个示例&#xff1a; SELECT * …

Android init.rc语言全解析

读懂init语言并不难,难的是有人愿意去做这个事情然后分享出来,如果不是接触到这块的知识,没有谁会专门研究这个,这也是为什么我写第一篇initrc文章到现在过去了7年多了一直没更新的原因,经常有小伙伴私信我希望我写一篇关于init文件解读的文章,或者是想请教下如何在custo…

vue搭建项目、创建登录页面和后台交互之引入axios

目录 1. 搭建项目 1.1 使用vue-cli创建项目 1.2 通过npm安装element-ui 1.3 导入组件 2 创建登录页面 2.1 创建登录组件 2.2 引入css&#xff08;css.txt&#xff09; 2.3 配置路由 2.4 在Login组件中将提交按键调整为100%宽度 2.5 运行效果 3. 后台交互 3.1 引入ax…

北京跨境电商开发

受疫情影响&#xff0c;许多国际航班停运&#xff0c;民众只能通过网络购买海外商品。越来越多的商家嗅到了这个商机&#xff0c;于是扩大经营范围。2021年以来&#xff0c;跨境电商投资不断升温&#xff0c;随着SaaS体系的完善&#xff0c;需求也在增加。跨境电商在市场上的潜…