Android开发handler源码再次解读记录

news/2025/3/10 16:18:30/文章来源:https://www.cnblogs.com/yongfengnice/p/18762954

源代码解读

  1. 开启线程HandlerThread首先是run方法跑起来,run方法里主要做两件事情,一个是创建Looper,一个是循环Looper
public class HandlerThread extends Thread {@Overridepublic void run() {Looper.prepare();  //创建LoopermLooper = Looper.myLooper();  // 然后获取Looper,也就是线程持有Looper。业务代码创建handler的时候调用getLooper()就是得到的这个LooperLooper.loop();  //启动循环Looper}
}
  1. 看看Looper.prepare()是怎么样给当前线程创建唯一的Looper对象的?
public final class Looper {// Looper类有个static的全局变量sThreadLocal,也就是说所有Looper对象共享这个全局变量sThreadLocal。这个变量用法相当于一个Map,它的key是当前线程Thread,键值是Looper。static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();private static void prepare(boolean quitAllowed) {// 判断当前线程是否已经有Looper了?如果不为空就有有了,那就直接报错,就是这里确保了一个线程只能有一个Looper。// 面试的时候人家问你一个线程最多有几个Looper知道怎么回答了吧?(回答:一个线程对应一个Looper,通过Looper类的全局变量sThreadLocal确保实现的)if (sThreadLocal.get() != null) {throw new RuntimeException("Only one Looper may be created per thread");}// 创建new Looper()放在全局变量存着sThreadLocal.set(new Looper(quitAllowed));}// 获取Looper对象自然是从这个全局变量直接拿了public static @Nullable Looper myLooper() {return sThreadLocal.get();}//Looper构造函数直接创建了一个消息队列MessageQueue。那就是一个Looper对应一个消息队列了,面试的时候懂了吧?//那一个线程对应多少个消息队列呢?回答:肯定是一个啊,上面不是说了一个线程只有一个Looper了吗?那一个Looper就一个消息列表,所以一个线程就只有一个消息队列!private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);  // Looper支持MessageQueue消息对应哦,上面备注说了线程持有Looper,那就是线程持间接有MessageQueue了(thread.mLooper.mQueue嘛,这个对后面handler消息派发流程的理解有用哦)mThread = Thread.currentThread();}
}
  1. 看看Looper.loop()干了啥,死循环为什么主线程卡住?
public final class Looper {//for (;;) {} 拿到队列后进去for死循环public static void loop() {final Looper me = myLooper(); //获取消息队列for (;;) {  //死循环消息队列,看到了吧?为什么主线程不会卡住呢?继续往下看,使用了linux的epoll-wait机制实现if (!loopOnce(me, ident, thresholdOverride)) {return;}}}//private static boolean loopOnce(final Looper me, final long ident, final int thresholdOverride) {Message msg = me.mQueue.next(); // might block,队列的next方法里使用了linux的epoll-wait机制实现,如果没有消息的时候会进去等待状态,直到超时或者调用了nativeWake(mPtr)方法msg.target.dispatchMessage(msg);  //这里的target就是hanlder,看后面的分析就知道}
}
public final class MessageQueue {Message next() {int nextPollTimeoutMillis = 0;//一开始为0,nativePollOnce第一次调用后立即返回for (;;) {//底层使用epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);实现nativePollOnce(ptr, nextPollTimeoutMillis);final long now = SystemClock.uptimeMillis();Message msg = mMessages; //mMessages是消息队列头指针if (msg != null && msg.target == null) { //如果target为空说明是barrier屏障消息do {prevMsg = msg;msg = msg.next;} while (msg != null && !msg.isAsynchronous());  //if条件target为空遇到了屏障消息后,找到最新异步消息。//这里可能有个面试题:怎么保证UI刷新消息优先执行?回答:通过屏障消息+异步消息实现,确保异步消息优先执行。}if (now < msg.when) {// Next message is not ready.  Set a timeout to wake up when it is ready.nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);  //next消息时间还没到的话,立即下一次poll_wait等待时间}else{msg.markInUse();return msg;}}  }
}
  1. 一个线程(一个Looper)对应几个Handler?回答:这里就不是只能对应一个了哦,可以多个,你创建了几个就对应几个啊
//创建了几个就对应几个啊,但是你发现没有,每次创建Handler都需要自己Looper的。这个Looper.getMainLooper()可是同一个Looper哦。也就是多个Handler可以共用一个Looper(也共用同一个HandlerThread,共用同一个MessageQueue)。
Handler handler1 = new Handler(Looper.getMainLooper());
Handler handler2 = new Handler(Looper.getMainLooper());
Handler handler3 = new Handler(Looper.getMainLooper());
//Handler内部持有mLooper和mQueue哦
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async, boolean shared) {mLooper = looper;mQueue = looper.mQueue;mCallback = callback;mAsynchronous = async;mIsShared = shared;
}
//handler.post一个消息,最终都会调用
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {if (delayMillis < 0) {  //指定时间为负数也没用,会帮你修复为0delayMillis = 0;}return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); //SystemClock.uptimeMillis()是手机开机时间哦,不是系统时间。所以为什么修改了系统时间还能确定消息的顺序
}public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {MessageQueue queue = mQueue;return enqueueMessage(queue, msg, uptimeMillis);
}private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) {msg.target = this;  //看到了吧?target就是this哦,这个this就是handler对象哦。注意:同步消息和异步消息都有target,但是屏障消息是没有target的。msg.workSourceUid = ThreadLocalWorkSource.getUid();if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);  //最后调用了队列的enqueueMessage方法将消息添加到队列,等待Looper的消费了。所以为什么Handler构造函数里通过Looper获取了mQueue知道了吧。
}

handler流程图

总结概括

  1. 一个HandlerThread线程只有一个Looper,一个Looper只有一个MessageQueue。Looper通过static类型的sThreadLocal确保每个HandlerThread线程仅有一个Looper。
  2. Handler是可以有多个的,不是只能有一个哦,但是创建Handler的时候需要Looper作为参数,传递的是哪个Looper,最后消息就是进去哪个Looper对应的消息队列。

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

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

相关文章

方法的声明和调用

方法:静态方法和实例方法,静态方法和类名绑定,实例方法可以进行实例化调用 方法属于类成员,不能独立与之外,在声明的时候必须在类里面进行方法的声明 方法的声明和调用:方法调用的格式与方法声明时的格式必须相同, 构造器:

自动化解决CertJava安全编码在网络安全开发中的应用

CERT是指软件工程研究所(Software Engineering Institute)发布的Java安全编码标准,主要目的是帮助开发者避免常见的安全漏洞,从而推出 CERT Java Coding Standard(JAVA安全编码标准)。​ 近十年来,在计算机系统中考虑安全性已经是一个严肃的问题。过去十年的网络的爆炸性…

【DeepSeek+dify+Ollama打造私有化RAG 01】

本地部署DeepSeek-RI打造自己的私有知识库 一、本地部署需要借助Ollama,Ollama是一个开源框架,专为在本地机器上便捷部署和运行大模型语言模型LLLM而设计 官网:https://ollama.com/ 1、安装ollama 官网下载后,直接就可以进行安装,安装后再终端输入:olllama2、 本地部署Dee…

网易邮箱如何用大数据任务调度实现海量邮件数据处理?Apache DolphinScheduler用户交流会上来揭秘!

一定不要错过这场精彩的线上用户交流会!在这里,你将有机会深入了解网易邮箱关于Apache DolphinScheduler平台的实际应用案例,汲取大厂的前沿实践经验。你是否对大数据领域的前沿应用充满好奇?网易邮箱作为互联网大厂网易的重要业务线,在大数据应用方面有着诸多值得借鉴的实…

信创国产系统对国产芯片产业的推动作用

信创国产系统与国产芯片产业紧密相连,二者的协同发展对于我国信息技术产业的自主可控和安全稳定具有至关重要的意义。信创国产系统的崛起,正以一种前所未有的力量推动着国产芯片产业不断向前迈进,在提升产业竞争力、保障国家信息安全等方面发挥着不可忽视的作用。 创造市场需…

Apache DolphinScheduler项目2月份进展总结

各位热爱DolphinScheduler的小伙伴们,今年2月份的社区月报如期而至,更新了DolphinScheduler项目和社区在本月的重大进展,请查收! DolphinScheduler月度Merge Stars 感谢以下小伙伴在2025年2月期间为Apache DolphinScheduler社区做的精彩贡献(排名不分先后):@ruanwenjun,…

信创国产系统实施后的效果评估与改进方法

信创国产系统的实施是推动信息技术自主创新、保障国家信息安全的重要举措。随着信创国产系统在各个领域的广泛应用,对其实施后的效果进行科学评估并持续改进显得尤为关键。这不仅关系到系统能否稳定、高效运行,更关乎企业和国家在数字化转型过程中的战略布局与发展。通过合理…

3.10 lambda算法

1.1 表达式(expression)(可以把两个表达式写在一起组成一个新的表达式) 包含:变量(单个字母/多个字母);括号(表示是一个整体);λ和.描述函数(函数由λ和变量开头,然后是一个.,然后是表达式),λ没有特殊的含义,只是说函数由此开始,在λ后面,.前面的字母成为变…

pfastq-dump 软件的安装以及测试

pfastq-dump 软件的安装以及测试 001、官网:https://github.com/inutano/pfastq-dump002、下载最新版wget -c https://github.com/inutano/pfastq-dump/archive/refs/tags/v0.1.6.tar.gztar -xzvf pfastq-dump-0.1.6.tar.gzcd pfastq-dump-0.1.6/bin/chmod +x pfastq-dump 00…

7.9K star!跨平台开发从未如此简单,这个开源框架让APP开发效率飙升!

Lynx 是一个革命性的跨平台开发框架,使用 TypeScript 开发即可同时构建 iOS、Android 和 Web 应用。通过创新的布局引擎和原生渲染技术,让开发者用一套代码实现三端同屏效果,大大提升整体的开发效率!嗨,大家好,我是小华同学,关注我们获得“最新、最全、最优质”开源项目…

国内头部HR SaaS厂商的薪酬管理实践:以标准化功能满足复杂薪酬管理需求

易路的成功案例证明了其在薪酬数字化管理转型中的领导地位,为其他企业提供了宝贵的参考和启示。随着易路的不断创新和优化,我们有理由相信它将继续引领行业,帮助企业实现薪酬管理的战略性业务支撑,为企业在激烈的市场竞争中提供强大的人力资源支持,实现企业与员工的共同发…

源码安装Rpcapd,用于 wireshark 远程抓包

背景 libpcap 是一个基础且关键的网络数据包捕获库,为 Wireshark、tcpdump 等流行工具提供核心功能支持。其中,rpcapd(Remote Packet Capture Daemon)组件允许在远程系统上进行数据包捕获,这一功能让我们能够从一个中心位置监控多个远程网络接入点,而无需在每个监控点都部署…