Android Handler机制(四) Message源码分析
一. 简介
接上一篇文章:Android Handler机制(三) Looper源码分析 ,我们来继续分析一下Message源码
这一系列文章都是为了深入理解Handler机制. Message 作为消息传递的载体,源码主要分为以下
几个部分:
1. 操作数据相关,类似 getter()和 setter()这种方法还有之前提到过的 what 和 obj 这类属性。
2. 创建与回收对象实例相关,除了用关键字 new 外,其他得到对象实例的方法。
3. 其他工具类性质的扩展方法。
整个类代码大概600多行, 接下来就开始一起分析下
二. Message数据属性与方法
我们先看看 Message 源码有哪些可供我们使用的属性
public int what :开发者可自定义的消息标识代码,用于区分不同的消息。
public int arg1: 如果要传递的消息只有少量的 integer 型数据,可以使用 这个属性。
public int arg2 :同上面 arg1。
public Object obj :开发者可自定义类型的传输数据。
上面四个属性作为常用的消息传递的数据载体可直接赋值,例如 msg.arg1 = 100;。基本可以满足我们日常开发中简单消息传递。
如果上面几个数据属性不能满足我们的需求,可以使用扩展数据:Bundle 来传递
源码中见Bundle的方法:
public Bundle getData() {if (data == null) {data = new Bundle();}return data;}public void setData(Bundle data) {this.data = data;}
这段代码也没什么逻辑好分析的,值得一提就是 Bundle data 不是 public 的,
所以我们不能直接操作这个属性,需要通过上面二个方法操作数据, 举个例子:
Bundle bundle = new Bundle();
bundle.putString("String","value");
bundle.putFloat("float",0.1f);
Message msg = Message.obtain();
msg.setData(bundle);
关于Bundle的API使用
请查看参考此链接:Bundle API
三. 创建Message 对象的基本方法
首先来看下Message类的构造方法
/** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).*/public Message() {}
从注释来看, 官方推荐我们使用Message.obtain()来获取Message对象
那这个 obtain()方法干了什么呢?其实就是内部维持了一个链表形式的 Meesage 对象 缓存池,这样会节省重复实例化对象产生的开销成本。
数据结构中的链表一个单元有两个值,当前单元的值 (head)和下一个单元的地址指针(next),如果下一个单元不存在那么 next 就是 null
所以,想要实现 Message 对象链表式缓存池就需要额外的两个 Message 类型的 引用 head 和 next,都说了叫缓存池,所以把 head 叫 pool 更合适一点。
有了链表的基础结构我们再想实例化对象的时候就可以先去链表缓存池中看看 有没有,有的话直接从缓存池中拿出来用,没有再 new 一个.
我们主要来分析一下 Message.obtain()方法
// 用于标识当前对象是否存在于缓存池,0 代表不在缓存池中
int flags;/*
这个常量是供上面的 flags 使用的,它表示 in use(正在使用)状态
如果 Message 对象被存入了 MessageQueue 消息队列排队等待 Looper
处理或者被回收到缓存池中等待重复利用时,那么它就是 in use(正在使 用)状态
只有在 new Message()和 Message.obtain()时候才可以清除掉 flags 上 的 in use 状态
*/
static final int FLAG_IN_USE = 1 << 0;/*
静态常量对象,通过 synchronized (sPoolSync)让它作为线程并发操作时的锁确保同一时刻只有一个线程可以访问当前对象的引用
*/
private static final Object sPoolSync = new Object();//当前链表缓存池的入口,装载着缓存池中第一个可用的对象
private static Message sPool;// 链表缓存池中指向下一个对象引用的 next 指针
Message next;// 当前链表缓存池中对象的数量
private static int sPoolSize = 0;/*** Return a new Message instance from the global pool. Allows us to* avoid allocating new objects in many cases.
从缓存池中拿出来一个 Message 对象给你, 可以让我们在许多情况下避免分配新对象。*/public static Message obtain() {
// 上锁,这期间只有一个线程可以执行这段代码synchronized (sPoolSync) {
// pool 不等于空就说明缓存池中还有可用的对象,直接取出来if (sPool != null) {
// 声明一个 Message 引用指向缓存池中的 pool 对象Message m = sPool;
// 让缓存池中 pool 引用指向它的 next 引用的对象sPool = m.next;
// 因为该对象已经从缓存池中被取出,所以将 next 指针置空m.next = null;
// 将从缓存池中取出的对象的 flags 的 in use 标识清除掉m.flags = 0; // clear in-use flag
// 缓存池中 Message 对象数量减去一个sPoolSize--;return m;}}
// 如果缓存池中没有可用的对象就 new一个return new Message();}
理论上我们希望 sPool 引用指向了链表缓存池中的第一个对象,让它作为整个缓存池的出入口。所以我们把它设置成 static 的,这样它就与实例化出来的对象 无关,也就是说无论我们在哪个 Message 对象中进行操作,sPool 还是 sPool。
再来梳理一下:
静态方法 obtain()的代码逻辑流程:
先判断缓存池是不是空的:if(sPool != null),如果是空的就直接:return new Message();,
不是空的就声明一个引用让它指向缓存池第一个对象:Message m = sPool;,
而缓存池的链表头部 sPool 引用就指向了链表中下一个对象:sPool = m.next;,因为这个时候缓存池中第一个对象已经取出交给了引用 Message m, 所以需要清除掉这个对象身上的特殊标识,
包括缓存池中的 next 引用和用来标 记对象状态的 flags值:m.next = null; m.flags = 0;,
最后将缓存池中的对象 数量减一:sPoolSize--;
用图表示更简洁一点:
阶段一: 初始状态, sPool 指向链表头部
阶段二: 声明一个Message 引用 m 也指向链表头部
阶段三: 让sPool 指向链表下一个对象
阶段四: 让m引用指向对象的next=null, flag 清零.
到这里,就把消息缓存池中的头部消息取出来了
分析到这里我们知道了为什么官方推荐我们使用 Message.obtain()得到对象 了,因为它是在缓存池中取出来重复利用的,但是通过上面代码也看可以看到,只有缓存池里有东西时也就是 sPool != null 的时候才可以取,Message 是怎么 把对象回收到缓存池中的呢?
四. 回收 Message 对象到缓存池的方法
阅读源码后发现有一个 public void recycle()方法用于回收 Message 对象,但 是它也牵扯出了一堆其他方法与属性:
// 缓存池最大存储值
private static final int MAX_POOL_SIZE = 50;// 区分当前 Android 版本是否大于或者等于 LOLLIPOP 版本的全局静态变量,默认初始值为 true
private static boolean gCheckRecycle = true;/*** 用于区分当前 Android 版本是否大于或者等于 LOLLIPOP 版本 * 内部隐藏方法,在 APP启动时就会执行该方法,开发者是不可见的 * @hide */
public static void updateCheckRecycle(int targetSdkVersion) {if (targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {gCheckRecycle = false;}}//* 判断当前对象的 flags 是否为 in-use 状态/*package*/ boolean isInUse() {return ((flags & FLAG_IN_USE) == FLAG_IN_USE);}/*
调用这个方法后,当前对象就会被回收入缓存池中。
你不能回收一个在 MessageQueue 排队等待处理或者正在交付给 Handler 处理的 Message 对象
说白了就是 in-use 状态的不可回收
*/
public void recycle() {
// 判断当前对象是否为 in-use 状态if (isInUse()) {
// 如果当前版本大于或者等于 LOLLIPOP 则抛出异常if (gCheckRecycle) {throw new IllegalStateException("This message cannot be recycled because it "+ "is still in use.");}
// 如果当前版本小于 LOLLIPOP 什么也不干直接结束方法return;}// 回收 Message 对象recycleUnchecked();}/*** Recycles a Message that may be in-use.* Used internally by the MessageQueue and Looper when disposing of queued Messages.*/@UnsupportedAppUsagevoid recycleUnchecked() {// Mark the message as in use while it remains in the recycled object pool.//回收缓存池中消息,标记flag为FLAG_IN_USE // Clear out all other details. //其他属性全部清除flags = FLAG_IN_USE;what = 0;arg1 = 0;arg2 = 0;obj = null;replyTo = null;sendingUid = UID_NONE;workSourceUid = UID_NONE;when = 0;target = null;callback = null;data = null;// 上锁synchronized (sPoolSync) {// 如果当前缓存池对象中的数量小于缓存池最大存储值(50)就存入缓存池中if (sPoolSize < MAX_POOL_SIZE) {// 存入缓存池next = sPool;sPool = this;// 缓存池数量加 1sPoolSize++;}}}
上面代码的逻辑很清晰,执行 recycle()方法后先判断当前对象是否为 in-use 状 态:if (isInUse()),如果是 in-use 状态的话当前 Android 版本是 LOLLIPOP(5.0) 版本之前直接结束程序,LOLLIPOP及之后版本抛出异常。
如果当前对象不是in-use 状态,那么就执行 recycleUnchecked()方法先将它切换到 in-use 状态:flags = FLAG_IN_USE;
再把所有的数据属性全部清除,最后把对象存入缓存池链表中。
回收 Message对象 流程图:
五. 我们需要手动回收消息嘛?
现在我们知道了通过执行 recycle()方法回收 Message 对象,但是如果要为每个 Message 对象都进行手动回收岂不是很麻烦?
庆幸的是开发人员也想到了这一点,从源码中可以看到其实最终真正执行回收操 作的调用 recycleUnchecked()方法,且注释中告诉我们 MessageQueue 和 Looper 内部也会调用该方法执行回收。
这里先说一个结论,MessageQueue 和 Looper 内部分发处理消息时,当它们得知 当前这个 Message 对象已经使用完毕后就会直接调用 recycleUnchecked()方法 将它回收掉.
MessageQueue中的相关代码:
//MessageQueue.java中 removeMessagexxx 都会调用recycleUnchecked()方法
private void removeAllMessagesLocked() {Message p = mMessages;while (p != null) {Message n = p.next;//消息回收p.recycleUnchecked();p = n;}mMessages = null;}
Looper 中的相关代码:
public static void loop() {.....for (;;) {//从MessageQueue中取出消息Message msg = queue.next(); // might blocktry {//分发消息msg.target.dispatchMessage(msg);if (observer != null) {observer.messageDispatched(token, msg);}.....//消息分发完之后,回收消息msg.recycleUnchecked();}}
所以,如果我们用实例化 Message 对象是放入 Handler 中去传消息的,那么我们 就不需要手动回收,他们内部自己就回收了。
六. 包含 Handler 参数的 obtain()方法
给 Message 内部装了一个 Handler 起了什么作用呢?首先,我们可以通过上面讲 解我们可以得出以下已知的结论:
1. 表面上看我们使用 Handler 发送消息后,消息直接传回到了 Handler 内部的 handleMessage(Message msg)方法中。
2. 实际上是先把消息传入了 MessageQueue 中,Looper 再从 MessageQueue 依次取出 消息分发给 Handler。
3. Looper 是线程独立的, Looper 和 MessageQueue 是一对一的。 但是,你有没有想过 Looper 和 Handler 是不是一对一的?答案:不是, MessageQueue 只负责队列消息,Looper 只负责取出消息分发。他们的功能很明 确而且通用。 所以,无论当前线程有多少个 Handler,同样都只有一个 Lopper 和一个 MessageQueue。
解释图:
提个问题:
Question: 既然每个线程只有一个 Looper 和 MessageQueue 的话那么 Looper 分发消息的时 候要如何判断当前这个 Message 是哪个 Handler 的呢?
Answer: 所以开发人员就给 Message内部配置了一个Handler属性,这样Looper分发消息时直接调用Messgae 内部的 Handler 属性就能找到它对应的 handleMessage(Message msg)接收消息的 方法了.
源码很简单,就是在空参方法 obtain()基础上加了个 Handler 属性,还有它的 getter()和 setter():
//handler对象
Handler target;/*** Same as {@link #obtain()}, but sets the value for the <em>target</em> member on the Message returned.* @param h Handler to assign to the returned Message object's <em>target</em> member.* @return A Message object from the global pool.*/public static Message obtain(Handler h) {Message m = obtain();//把handler与Message关联上m.target = h;return m;}public void setTarget(Handler target) {this.target = target;}/*** Retrieve the {@link android.os.Handler Handler} implementation that* will receive this message. The object must implement* {@link android.os.Handler#handleMessage(android.os.Message)* Handler.handleMessage()}. Each Handler has its own name-space for* message codes, so you do not need to* worry about yours conflicting with other handlers.*/public Handler getTarget() {return target;}
还有一个地方也可以找到他们 Message 与 Handler 关联的代码, 就是Handler发送消息的时候就已经关联上了,在 Handler.java文件中:
//1.发送消息public final boolean sendMessage(@NonNull Message msg) {return sendMessageDelayed(msg, 0);}//2. 接着调用
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);}// 3.消息关联上handlerprivate 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);}
这样子就知道 这个消息的源头是与哪个Handler相关联的, 当然处理消息时,自然就回到了对应线程的Hanlder的 handleMessage方法中了. 这样就保证消息发送和处理的准确性了.
相关文章:

Android Handler机制(四) Message源码分析
一. 简介 接上一篇文章:Android Handler机制(三) Looper源码分析 ,我们来继续分析一下Message源码 这一系列文章都是为了深入理解Handler机制. Message 作为消息传递的载体,源码主要分为以下 几个部分: 1. 操作数据相关,类似 getter()和 setter()这种…...

【Git】git命令(全)
Git1、本地操作2、版本管理3、远端仓库4、分支管理5、缓存stash6、遗留rebase7、标签管理8、解决冲突9、参考教程10、示例代码1、本地操作 Linux安装git:yum install git查看git版本 git version查看git设置 git config --list设置git属性 git config --global初始…...
软考论文-成本管理(1)
成本管理 1.成本管理的主要内容? 规划成本:制定一个成本管理的计划。估算成本:根据项目范围说明书,项目管理计划和wbs等文档,采用xxx方法进行估算成本成本预算:可以算工作包的费用,制定预算和…...

Java 多线程 --- 锁的概念和类型划分
Java 多线程 --- 锁的概念和类型划分锁的概念乐观锁与悲观锁公平锁与非公平锁什么是可重入锁独占锁与共享锁轻量级锁和重量级锁自旋锁 (Spinlock)锁的概念 锁可以将多个线程对共享数据的并发访问转换为串行访问, 这样一个共享数据一次只能被一个线程访问, 该线程访问结束后其他…...

python程序员狂飙上头——京海市大嫂单推人做个日历不过分吧?
嗨害大家好鸭!我是小熊猫~ 这个反黑剧其实火了很久了, 但是我现在才有空开始看 该说不说,真的很上头!!! 大嫂简直就像是干枯沙漠里的玫瑰 让人眼前一亮哇~~ 我小熊猫此时此刻就成为大嫂的单推人&…...

浅谈子网掩码、IP地址、网络地址之间关系
文章目录一、什么是子网掩码二、给定IP地址,如何求网络地址网络标识(net-id)和主机标识(host-id)计算步骤三、CIDR地址表示方法(Classless Inter Domain Routing)四、IP地址与MAC地址一、什么是子网掩码 在TCP/IP协议…...
前端优化的解决方案
能缓存的,尽量强缓存。减少HTTP请求数 使用外部引入的css和js文件,并且引入的css和js越少越好使用雪碧图(精灵图)img计算缩放也需要时间,使用base64编码将较小图片嵌入到样式表中,减少请求数因为iframe会阻…...

PYthon组合数据类型的简单使用
Python的数据类型有两种,基本数据类型和组合数据类型,组合数据类型在Python的使用中特别重要。 1.组合数据类型的分类: 2.序列类型 序列类型中元素存在顺序关系,可以存在数值相同但位置不同的元素。序列类型支持成员关系操作符&…...

【Java】P2 基础语法与运算符
Java 基础语法 运算符Java注释方法基本数据类型驼峰命名法Scanner类基本运算除法隐式转换逻辑运算符 以及 短路逻辑运算符三元运算符前言 上一节内容涵盖Java的基础知识,包含安装下载,JDK与JRE等。 链接:https://blog.csdn.net/weixin_43098…...

【并发基础】Java中线程的创建和运行以及相关源码分析
目录 一、线程的创建和运行 1.1 创建和运行线程的三种方法 1.2 三者之间的继承关系 二、Thread类和Runnable接口的区别 2.1 Runnable接口可以实现线程之间资源共享,而Thread类不能 2.2 实现Runnable接口相对于继承Thread类的优点 三、实现 Runnable 接口和实现 Call…...

Spark Shuffle
Shuffle : 集群范围内跨节点、跨进程的数据分发 分布式数据集在集群内的分发,会引入大量的磁盘 I/O 与网络I/O在 DAG 的计算中,Shuffle 环节的执行性能是最差的 , 会消耗所有类型的硬件资源 (CPU、内存、磁盘、网络) Spark 2.0 后,将 Shuff…...

Linux/MacOS 生成双击可执行文件
双击可执行文件包含两种:终端shell脚本 Unix可执行文件 1.终端shell脚本 随意新建一个文件(可使用command键N,前提是有已打开的文件),输入shell格式的测试代码,比如: #! /bin/sh echo “h…...

Ubuntu三种拨号方法
1.宽带拨号(PPPoE) (1)打开连接。关闭以太网连接,打开有线连接设置,取消勾选“自动连接”选项。 (2)配置连接。在终端输入命令sudo pppoeconf,会看到一系列配置信息,包括用户名、密码,配置完成后会有一些提示信息&…...

Vue-router的引入和安装
什么是Vue-Router?Vue路由器是Vue.js的官方路由器,它与Vue.js核心深度集成,使用Vue轻松构建单页应用程序变得轻而易举。功能包括:嵌套路线映射动态路由模块化,基于组件的路由器配置路由参数,查询࿰…...

无线WiFi安全渗透与攻防(四)之kismet的使用
系列文章 无线WiFi安全渗透与攻防(一)之无线安全环境搭建 无线WiFi安全渗透与攻防(二)之打造专属字典 无线WiFi安全渗透与攻防(三)之Windows扫描wifi和破解WiFi密码 kismet 如果要进行无线网络渗透测试,则必须先扫描所有有效的无线接入点。刚好在Kali Linux中&am…...

2023新版PMP考试有哪些变化?
对于2022年很多事情也都在发生,疫情也都没有完全结束,基金会已经开始通知下一场考试了,很多人也会担心新的考纲会不会给自己带来难度,其实这次六月份的考试很多人都内心已经知道了结果,所以这里也详细说一下新考纲的改…...
P8074 [COCI2009-2010#7] SVEMIR 最小生成树
[COCI2009-2010#7] SVEMIR 题目描述 太空帝国要通过建造隧道来联通它的 NNN 个星球。 每个星球用三维坐标 (xi,yi,zi)(x_i,y_i,z_i)(xi,yi,zi) 来表示,而在两个星球 A,BA,BA,B 之间建造隧道的价格为 min{∣xA−xB∣,∣yA−yB∣,∣zA−zB∣}\min\{|x_A-x_…...

10种常见网站安全攻击手段及防御方法
在某种程度上,互联网上的每个网站都容易遭受安全攻击。从人为失误到网络罪犯团伙发起的复杂攻击均在威胁范围之内。 网络攻击者最主要的动机是求财。无论你运营的是电子商务项目还是简单的小型商业网站,潜在攻击的风险就在那里。 知己知彼百战不殆&…...

为什么我选择收费的AdsPower指纹浏览器?
在决定开始用指纹浏览器之前,龙哥让我们团队的运营小哥找了市面上很多产品去测试。最后,还是决定用AdsPower。每个人的使用感受都不一样,龙哥就说说几个用得顺手的几个点。一、指纹环境强大 双内核引擎 市面上指纹浏览器内核都是基于谷歌Chro…...
Java输入输出和数组
一、问答题 1. 如何声明和创建一个一维数组? Int i[]new int[3] 2. 如何访问数组的元素? Int a[]new int a[3] for (int x0;x<a.length;x){ System.out.print(i[x]); } System.out.println(); 3.数组下标的类型是什么?最小的下标是什…...

stm32G473的flash模式是单bank还是双bank?
今天突然有人stm32G473的flash模式是单bank还是双bank?由于时间太久,我真忘记了。搜搜发现,还真有人和我一样。见下面的链接:https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...

智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...
多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验
一、多模态商品数据接口的技术架构 (一)多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如,当用户上传一张“蓝色连衣裙”的图片时,接口可自动提取图像中的颜色(RGB值&…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)
目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关࿰…...
大数据学习(132)-HIve数据分析
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言Ǵ…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)
Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败,具体原因是客户端发送了密码认证请求,但Redis服务器未设置密码 1.为Redis设置密码(匹配客户端配置) 步骤: 1).修…...
PAN/FPN
import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...

DeepSeek源码深度解析 × 华为仓颉语言编程精粹——从MoE架构到全场景开发生态
前言 在人工智能技术飞速发展的今天,深度学习与大模型技术已成为推动行业变革的核心驱动力,而高效、灵活的开发工具与编程语言则为技术创新提供了重要支撑。本书以两大前沿技术领域为核心,系统性地呈现了两部深度技术著作的精华:…...

使用SSE解决获取状态不一致问题
使用SSE解决获取状态不一致问题 1. 问题描述2. SSE介绍2.1 SSE 的工作原理2.2 SSE 的事件格式规范2.3 SSE与其他技术对比2.4 SSE 的优缺点 3. 实战代码 1. 问题描述 目前做的一个功能是上传多个文件,这个上传文件是整体功能的一部分,文件在上传的过程中…...