Android学习总结之handler源码级

一、核心类关系与线程绑定(ThreadLocal 的核心作用)

1. Looper 与 ThreadLocal 的绑定

每个线程的 Looper 实例通过 ThreadLocal<Looper> sThreadLocal 存储,确保线程隔离:

public final class Looper {// 线程本地存储,每个线程独有一个 Looperstatic final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();// 主线程 Looper(ActivityThread 中创建)static Looper sMainLooper; final MessageQueue mQueue; // 关联的消息队列final Thread mThread; // 绑定的线程private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread(); // 记录当前线程}// 线程首次调用,创建 Looper 并存储到 ThreadLocalpublic static void prepare() {prepare(false); // quitAllowed 默认 false(主线程不允许退出)}private static void prepare(boolean quitAllowed) {if (sThreadLocal.get() != null) { // 禁止重复创建throw new RuntimeException("Only one Looper may be created per thread");}sThreadLocal.set(new Looper(quitAllowed)); // 存入当前线程的 Looper}// 获取当前线程的 Looperpublic static @Nullable Looper myLooper() {return sThreadLocal.get();}
}
  • 主线程:Android 框架在 ActivityThread.main() 中自动调用 Looper.prepareMainLooper() 和 Looper.loop(),无需手动处理。
  • 子线程:必须手动调用 Looper.prepare()(创建 Looper 和 MessageQueue)和 Looper.loop()(启动消息循环),否则 Handler 无可用 Looper 会报错。
2. Handler 的构造与 Looper 关联

Handler 实例必须与一个 Looper 绑定,默认使用当前线程的 Looper(通过 Looper.myLooper() 获取):

public class Handler {final Looper mLooper; // 关联的 Looperfinal MessageQueue mQueue; // Looper 的消息队列final Callback mCallback; // 消息回调public Handler() {this(null, false);}public Handler(@Nullable 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(); // 获取当前线程的 Looper(必须已 prepare)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;}
}
  • 若子线程未调用 Looper.prepare(),创建 Handler 时会抛出 RuntimeException,这就是子线程必须先准备 Looper 的原因。

二、消息发送:从 Handler 到 MessageQueue 的入队

1. Message 的创建与重用(消息池机制)

Message 优先从消息池获取,避免频繁 GC:

public final class Message implements Parcelable {// 消息池头节点(静态,所有线程共享)private static Message sPool;// 消息池大小(最大 50 个)private static int sPoolSize = 0;// 下一个可用消息(形成单链表)@UnsupportedAppUsageMessage next;// 从消息池获取消息public static Message obtain() {synchronized (sPoolSync) { // 线程安全if (sPool != null) {Message m = sPool;sPool = m.next; // 取出头节点m.next = null; // 断开引用m.flags = 0; // 重置标志位sPoolSize--;return m;}}return new Message(); // 池空时新建}// 回收消息到池中(处理完后调用)void recycleUnchecked() {if (isInUse()) { // 确保未被使用if (gCheckRecycle) {throw new IllegalStateException("This message cannot be recycled because it "+ "is still in use.");}return;}recycle();}private void recycle() {if (mRecycled) { // 已回收过则不再处理return;}mRecycled = true;if (sPoolSize < MAX_POOL_SIZE) { // 最大 50 个next = sPool;sPool = this;sPoolSize++;}}
}

  • 优势:减少对象创建开销,提升性能,尤其适合高频发送消息的场景。
2. MessageQueue 的入队逻辑(enqueueMessage)

消息按 msg.when(执行时间)排序插入,形成一个 非严格 FIFO 的有序单链表

boolean enqueueMessage(Message msg, long when) {msg.target = this; // target 指向发送消息的 Handlermsg.workSourceUid = ThreadLocalWorkSource.getUid();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;if (p == null || when == 0 || when < p.when) { // 新消息时间更早,插入头部msg.next = p;mMessages = msg;needWake = mBlocked; // 当前队列是否阻塞,决定是否唤醒} else { // 找到合适位置插入(按 when 升序)needWake = mBlocked && p.target == null && msg.isAsynchronous();Message prev;for (;;) { // 遍历链表找到插入点prev = p;p = p.next;if (p == null || when < p.when) {break;}if (needWake && p.isAsynchronous()) {needWake = false;}}msg.next = p;prev.next = msg;}// 若队列阻塞且需要唤醒(如 Looper.loop() 在等待),通过 native 方法唤醒if (needWake) {nativeWake(mPtr);}}return true;
}

  • 关键点
    • synchronized (this) 确保多线程安全,不同线程的 Handler 发送消息到同一 MessageQueue 时不会冲突。
    • 消息按 when 排序,而非严格的 FIFO,支持延迟消息(如 postDelayed)。
    • 异步消息(msg.setAsynchronous(true))可打断同步消息的等待,优先处理。

三、消息循环:Looper.loop () 的无限循环

1. loop () 方法核心逻辑
public static void loop() {final Looper me = myLooper(); // 获取当前线程的 Looperif (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}final MessageQueue queue = me.mQueue; // 关联的消息队列// 确保主线程 Looper 不会被 GC 回收(Binder 机制相关)Binder.clearCallingIdentity();final long ident = Binder.clearCallingIdentity();for (;;) { // 无限循环,直到队列退出Message msg = queue.next(); // 取出消息(可能阻塞)if (msg == null) { // 队列退出(msg.next == null)return; // 退出循环}// 处理消息:通过 msg.target(Handler)分发msg.target.dispatchMessage(msg);// 回收消息到池中(非必须,系统自动处理)msg.recycleUnchecked();}
}
2. MessageQueue.next () 的阻塞与唤醒

next() 是消息循环的核心,通过 native 层实现阻塞等待:

Message next() {final long ptr = mPtr; // 指向 native 层的 MessageQueue 实例int pendingIdleHandlerCount = -1; // 首次调用时初始化为 -1int nextPollTimeoutMillis = 0;for (;;) {if (nextPollTimeoutMillis != 0) {Binder.flushPendingCommands();}// native 层阻塞等待消息,直到有消息或被唤醒nativePollOnce(ptr, nextPollTimeoutMillis);synchronized (this) {// 检查是否有消息待处理final long now = SystemClock.uptimeMillis();Message prevMsg = null;Message msg = mMessages;if (msg != null && msg.when > now) { // 消息未到执行时间,计算延迟nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);} else { // 有可执行的消息,取出头节点msg = mMessages;mMessages = msg.next;msg.next = null;if (DEBUG) Log.v(TAG, "Returning message: " + msg);nextPollTimeoutMillis = -1; // 下次立即 poll}if (msg != null) { // 有消息,返回给 Looperreturn msg;}// 无消息,检查是否退出mQuitting = true;return null;}}
}
  • 阻塞原理:通过 nativePollOnce 进入内核等待,当消息入队时(enqueueMessage 中调用 nativeWake)被唤醒。
  • 退出条件:调用 Looper.quit() 或 Looper.quitSafely() 时,mQuitting 设为 true,next() 返回 null,loop() 终止。

四、消息处理:Handler.dispatchMessage 的分发逻辑

消息最终由发送它的 Handler 处理,分发流程如下:

public void dispatchMessage(Message msg) {if (msg.callback != null) { // 优先处理 Message 的 Runnable(post(Runnable))handleCallback(msg);} else if (mCallback != null) { // 其次处理 Handler 的 Callbackif (mCallback.handleMessage(msg)) {return;}}handleMessage(msg); // 最后调用用户重写的 handleMessage()
}private static void handleCallback(Message message) {message.callback.run(); // 执行 post(Runnable) 传入的 Runnable
}
  • 三种处理方式
    1. Message 自带的 Runnable:通过 post(Runnable) 发送的消息,直接执行 Runnable.run()
    2. Handler 的 Callback:通过构造函数传入的 Callback,优先级高于 handleMessage
    3. 用户重写的 handleMessage:最常用的消息处理逻辑。

五、子线程使用 Handler 的完整流程(源码级示例)

// 子线程类
class WorkerThread extends Thread {private Handler mHandler;private Looper mLooper;// 获取 Handler(供外部发送消息)public Handler getHandler() {return mHandler;}@Overridepublic void run() {// 1. 准备 Looper(创建 Looper 和 MessageQueue)Looper.prepare();synchronized (this) {mLooper = Looper.myLooper(); // 保存当前线程的 LoopermHandler = new Handler() { // 创建 Handler 关联当前 Looper@Overridepublic void handleMessage(Message msg) {// 处理消息(子线程中执行)processMessage(msg);if (msg.what == MSG_QUIT) { // 退出消息mLooper.quit(); // 停止消息循环}}};notify(); // 通知主线程 Handler 已准备好}// 2. 启动消息循环Looper.loop();}
}// 主线程使用子线程 Handler
public class MainActivity extends AppCompatActivity {private WorkerThread mWorkerThread;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mWorkerThread = new WorkerThread();mWorkerThread.start();synchronized (mWorkerThread) {try {mWorkerThread.wait(); // 等待子线程准备好 Handler} catch (InterruptedException e) {e.printStackTrace();}}// 向子线程发送消息Message msg = Message.obtain();msg.what = WorkerThread.MSG_WORK;mWorkerThread.getHandler().sendMessage(msg);// 发送退出消息mWorkerThread.getHandler().sendMessage(Message.obtain(null, WorkerThread.MSG_QUIT));}
}
  • 关键步骤
    1. 子线程中调用 Looper.prepare() 创建 Looper 和 MessageQueue。
    2. 创建 Handler 时自动关联当前 Looper(因在 prepare() 之后调用)。
    3. 调用 Looper.loop() 启动消息循环,处理外部发送的消息。
    4. 通过 Looper.quit() 终止循环,避免子线程阻塞。

六、常见问题源码级解析

1. 为什么主线程可以直接创建 Handler?
  • 主线程(ActivityThread 所在线程)在启动时,框架自动调用了:
    public static void main(String[] args) {// ...Looper.prepareMainLooper(); // 准备主线程 LooperActivityThread thread = new ActivityThread();thread.attach(false, startSeq);Looper.loop(); // 启动主线程消息循环
    }
    

    因此主线程的 Looper 已存在,无需手动调用 prepare()
2. MessageQueue 真的是 “队列” 吗?
  • 从数据结构看,它是一个 单链表,而非传统的 FIFO 队列。消息按 msg.when 排序插入,保证按时间顺序执行,支持延迟消息。
3. postDelayed 不准时的根本原因?
  • postDelayed 计算延迟的基准时间是 SystemClock.uptimeMillis()(系统启动后非休眠时间),若设备休眠,休眠时间不计入延迟,导致实际执行时间晚于预期。
  • 此外,消息需等待前面的消息处理完成,若主线程被阻塞(如耗时操作),延迟消息会被阻塞。
4. 多线程发送消息到同一 MessageQueue 为何线程安全?
  • MessageQueue.enqueueMessage 使用 synchronized (this) 加锁,确保同一时间只有一个线程操作队列,避免并发问题。

七、Handler 机制的设计精髓

  1. 线程隔离:通过 ThreadLocal 保证每个线程独有 Looper 和 MessageQueue,避免资源竞争。
  2. 消息池:重用 Message 对象,减少内存分配和 GC 压力,提升性能。
  3. 有序调度:按时间排序的消息链表,支持延迟和异步消息,灵活控制执行顺序。
  4. 阻塞与唤醒:通过 native 层实现高效的等待与唤醒,避免 CPU 空转。

总结

     Handler 机制的核心是通过 Looper(消息循环)、MessageQueue(有序消息链表)、Handler(消息收发器) 的协作,实现线程间的安全通信。源码中大量使用 ThreadLocal、synchronized、单链表等技术,确保线程隔离、数据安全和性能优化。深入理解这些细节,能帮助开发者更好地处理线程通信、避免内存泄漏(如非静态内部类 Handler 导致的 Activity 泄漏),并在复杂场景中灵活运用 Handler 机制。

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

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

相关文章

群体智能优化算法-算术优化算法(Arithmetic Optimization Algorithm, AOA,含Matlab源代码)

摘要 算术优化算法&#xff08;Arithmetic Optimization Algorithm, AOA&#xff09;是一种新颖的群体智能优化算法&#xff0c;灵感来源于加、减、乘、除四种基本算术运算。在优化过程中&#xff0c;AOA 通过乘除操作实现全局探索&#xff0c;通过加减操作强化局部开发&#…

广告推荐算法:COSMO算法与A9算法的对比

COSMO算法与A9算法的概念解析 1. A9算法 定义与背景&#xff1a; A9算法是亚马逊早期为电商平台研发的核心搜索算法&#xff0c;主要用于优化商品搜索结果的排序和推荐&#xff0c;其核心逻辑围绕产品属性与关键词匹配展开。自2003年推出以来&#xff0c;A9通过分析商品标题…

EasyExcel 数据字典转换器实战:注解驱动设计

一、场景痛点与解决方案 1. 问题背景 在 Excel 导入导出场景中&#xff0c;开发者常面临以下问题&#xff1a; 数据可读性差&#xff1a;数据库存储的字典值&#xff08;如 1、true&#xff09;直接导出时难以理解双向转换复杂&#xff1a;导入时需将用户输入的标签反向解析…

五种音频器件综合对比——《器件手册--音频器件》

目录 音频器件 简述 1. 扬声器&#xff08;Speakers&#xff09; 2. 麦克风&#xff08;Microphones&#xff09; 3. 放大器&#xff08;Amplifiers&#xff09; 4. 音频接口&#xff08;Audio Interfaces&#xff09; 5. 音频处理器&#xff08;Audio Processors&#xff09…

红宝书第二十九讲:详解编辑器和IDE:VS Code与WebStorm

红宝书第二十九讲&#xff1a;详解编辑器和IDE&#xff1a;VS Code与WebStorm 资料取自《JavaScript高级程序设计&#xff08;第5版&#xff09;》。 查看总目录&#xff1a;红宝书学习大纲 一、核心区别&#xff1a;编辑器与IDE 代码编辑器&#xff08;如VS Code&#xff09…

虚拟电商-话费充值业务(五)充值成功逻辑和网络异常重试逻辑

一、网络异常重试逻辑编写 如果在对接供应商的过程中出现了网络异常&#xff0c;我们需要做一个补偿机制&#xff0c;在任务类型枚举类&#xff1a;TaskTypeEnum中有一种业务状态码是针对远程调用失败的 步骤一&#xff1a;在对接供应商的方法&#xff1a;SupplierServiceImp…

从零构建大语言模型全栈开发指南:第四部分:工程实践与部署-4.3.3低代码开发:快速构建行业应用(电商推荐与金融风控案例)

👉 点击关注不迷路 👉 点击关注不迷路 👉 点击关注不迷路 文章大纲 从零构建大语言模型全栈开发指南-第四部分:工程实践与部署4.3.3 低代码开发:快速构建行业应用(电商推荐与金融风控案例)1. 低代码与AI结合的核心价值2. 电商推荐系统案例2.1 技术架构与实现2.2 性能…

Table as Thought论文精读

标题&#xff1a;Table as Thought: Exploring Structured Thoughts in LLM Reasoning 作者&#xff1a;Zhenjie Sun, Naihao Deng, Haofei Yu, Jiaxuan You 单位&#xff1a;University of Illinois Urbana-Champaign, University of Michigan 摘要&#xff1a; llm的推理…

ubuntu18 server版花屏问题

新搞了一台dellT150的塔式服务器&#xff0c;装的ubuntu18 server版。 开机后遇到花屏&#xff0c;或者卡在开机界面的问题&#xff0c;和售后技术沟通这个情况是ubuntu自带的显卡驱动包兼容问题。需要做如下设置&#xff1a; 解决&#xff1a; 1.开机&#xff0c;连续按下e…

【MySQL】理解MySQL的双重缓冲机制:Buffer Pool与Redo Log的协同之道

在数据库系统中&#xff0c;内存与磁盘的读写性能差距始终是需要解决的核心问题。当注意到Redo Log和Buffer Pool都采用"先写内存再刷盘"的设计时&#xff0c;一个自然的问题浮现&#xff1a;既然两者都需要维护内存数据并定期持久化&#xff0c;为何需要双重缓冲机制…

PMP考试改革解读:新题型+5A通关秘籍

2024年&#xff0c;项目管理协会&#xff08;PMI&#xff09;对PMP考试进行了重大调整&#xff0c;从考试形式、题型分布到知识领域均进行了优化升级。本文结合PMI官方公告与一线教研经验&#xff0c;深度解析改革要点&#xff0c;并提供针对性通关策略&#xff0c;助你高效冲刺…

【Django】教程-10-ajax请求Demo,结合使用

【Django】教程-1-安装创建项目目录结构介绍 【Django】教程-2-前端-目录结构介绍 【Django】教程-3-数据库相关介绍 【Django】教程-4-一个增删改查的Demo 【Django】教程-5-ModelForm增删改查规则校验【正则钩子函数】 【Django】教程-6-搜索框-条件查询前后端 【Django】教程…

RabbitMQ高级特性1

RabbitMQ高级特性1 一.消息确认1.消息确认机制2.手动确认代码肯定确认否定确认1否定确认2Spring中的代码 二.持久性1.交换机持久化2.队列的持久化3.消息的持久化非持久化代码实现三方面都持久化&#xff0c;数据也会丢失 三.发送方确认1.Confirm确认模式2.return返回模式 四.总…

Java网络编程NIO

一、NIO是什么? NIO可以说是比BIO更强大的IO&#xff0c;可以设置非阻塞模式&#xff08;通过事件的方式监听数据的到来&#xff09; BIO是基于socket通信&#xff0c;一个线程对应一个socket连接&#xff0c;读取数据要一直等待 NIO是基于channel通信&#xff0c;一个线程管…

【动态规划】二分优化最长上升子序列

最长上升子序列 II 题解 题目传送门&#xff1a;AcWing 896. 最长上升子序列 II 一、题目描述 给定一个长度为 N 的数列&#xff0c;求数值严格单调递增的子序列的长度最长是多少。 输入格式&#xff1a; 第一行包含整数 N第二行包含 N 个整数&#xff0c;表示完整序列 输…

Dify接口api对接,流式接收流式返回(.net)

试了好多种方法除了Console.WriteLine()能打印出来&#xff0c;试了好些方法都不行&#xff0c;不是报错就是打印只有一行&#xff0c;要么就是接收完才返回...下面代码实现调用api接收流式数据&#xff0c;并进行流式返回给前端&#xff1a; using Furion.HttpRemote; using …

19-元素显示模式及浮动(CSS3)

知识目标 掌握标准文档流的解析规则掌握元素的显示模式掌握元素浮动属性语法与使用掌握浮动塌陷解决方法 1. 标准文档流 2. 元素显示模式 元素显示模式就是元素&#xff08;标签&#xff09;以什么方式进行显示&#xff0c;比如<div>独占一行&#xff0c;一行可以放多…

HTML jQuery 项目 PDF 批注插件库在线版 API 示例教程

本文章介绍 HTML && jQuery Web项目中 PDF 批注插件库 ElasticPDF 在线版 API 示例教程&#xff0c;API 包含 ① 导出批注后PDF数据&#xff1b;② 导出纯批注 json 数据&#xff1b;③ 加载旧批注&#xff1b;④ 切换文档&#xff1b;⑤ 切换用户&#xff1b;⑥ 清空批…

CATIA装配体全自动存储解决方案开发实战——基于递归算法的产品结构树批量处理技术

一、功能定位与技术架构 本工具针对CATIA V5装配体文件管理场景&#xff0c;实现了一套全自动递归存储系统&#xff0c;主要功能包括&#xff1a; ​智能路径选择&#xff1a;通过Tkinter目录对话框实现可视化路径选择​产品结构递归解析&#xff1a;深度优先遍历装配体中的子…

C#:接口(interface)

目录 接口的核心是什么&#xff1f; 1. 什么是接口&#xff08;Interface&#xff09;&#xff0c;为什么要用它&#xff1f; 2. 如何定义和使用接口&#xff1f; 3.什么是引用接口&#xff1f; 如何“引用接口”&#xff1f; “引用接口”的关键点 4. 接口与抽象类的区…