Android Handler 消息机制

常用场景:
子线程发送Message 
主线程处理Message

子线程发送消息

    public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {Message msg = Message.obtain();msg.what = what;return sendMessageDelayed(msg, delayMillis);}public final boolean sendEmptyMessage(int what){return sendEmptyMessageDelayed(what, 0);}

不管那种方式发送小时最终都走到 sendMessageAtTime

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

通过enqueueMessage将消息按照时间先手插入到MessageQueue中

主线程处理消息

应用启动:

frameworks\base\core\java\android\app\ActivityThread.java

main方法中关注Looper.prepareMainLooper()和Looper.loop().

Looper.prepareMainLooper()

 public static void prepareMainLooper() {prepare(false);synchronized (Looper.class) {if (sMainLooper != null) {//Looper的唯一性throw new IllegalStateException("The main Looper has already been prepared.");}sMainLooper = myLooper();}}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));}private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread();//绑定当前线程}MessageQueue(boolean quitAllowed) {mQuitAllowed = quitAllowed;mPtr = nativeInit();}void quit(boolean safe) {if (!mQuitAllowed) {throw new IllegalStateException("Main thread not allowed to quit.");}
......
}

1. prepare创建 Looper 并设置给了sThreadLocal 后面loop中要获取

2.prepare创建不可退出的MessageQueue(因为在主线程)

Looper.loop()

 public static void loop() {final Looper me = myLooper();......me.mInLoop = true;final MessageQueue queue = me.mQueue;for (;;) { //死循环Message msg = queue.next(); // might block......try {msg.target.dispatchMessage(msg);if (observer != null) {observer.messageDispatched(token, msg);}dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;} catch (Exception exception) {if (observer != null) {observer.dispatchingThrewException(token, msg, exception);}throw exception;} finally {ThreadLocalWorkSource.restore(origWorkSource);if (traceTag != 0) {Trace.traceEnd(traceTag);}}......msg.recycleUnchecked();}}public static @Nullable Looper myLooper() {return sThreadLocal.get();}

1.Looper持有了当前线程的MessageQueue

2.通过queue.next() 获取当前时间下一次要执行的Message

3.处理消息 msg.target.dispatchMessage(msg) 分发到内部类中处理(msg.target就是当前消息绑定的Handler)

4. 消息回收(消息复用) msg.recycleUnchecked()

流程:应用启动-->ActivityThread main 启动 --> 准备Looper  --> Looper死循环  一直取队列中的消息  -->处理消息 --> 消息回收处理

PS: 应用异常(Runtime)时压栈最底下ActivityThread.main()   倒数第二行Looper.loop()    这也印证了启动的顺序

Handler创建

常见创建:

Handler handler=new Handler(){@Overridepublic void handleMessage(Message msg) {super.handleMessage(msg);}
};public Handler() {this(null, false);}public Handler(@Nullable Callback callback, boolean async) {mLooper = Looper.myLooper();//获取Looperif (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;}public interface Callback {boolean handleMessage(@NonNull Message msg);}

消息创建

new Message

   /** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).*/public Message() {}

Google 没有详细说明new  Message 当通过我们实际直接new 就完事了无需多言,不过Google推荐使用Message.obtain() 去创建

方法一:
Message msg = Message.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();}
方法二:
Handler handler = new Handler();
Message msg = handler.obtainMessage();//绑定了handler
--------------------------------源码-------------------------------
public final Message obtainMessage(){return Message.obtain(this);// this==handler}public static Message obtain(Handler h) {Message m = obtain();m.target = h;return m;}

不管那种方式创建最终都是走无参的obtain方法
1.sPool等于空时通过new 创建Message
2.sPool 是在Looper dispatch 出去后通过recycleUnchecked清空后到Message

3.sPool最前面的空Messsage返回回去,sPool指针后移队列到下一个保证下一个obtain可以正确获获取到sPool中的空Message

Message和handler的绑定
1.可以在创建Message是绑定,参考Message创建的方法二
2.消息发送是绑定
回到发送Message

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

总结:

 

handler机制就是一个传送带
1MessageQueue就是堆放货物履带。
2)ActivityThread电机给履带滚动的动力
3)loop中的死循环就像开关,死循环开启履带滚动 轮询messageQueue按照时间分发出去
4)Message就是要传输得货物

同步问题

Handler是如何保证自己是线程安全?
从总结图可以看出整个过程只要保证MessageQueue 中msg的入队和出队即可
enqueueMessage方法中 通过synchronized (this) {} 锁住了关键的代码块
1,synchronized 是内置锁   锁的lock 和unlock 是系统(JVM)执行
2.  锁的是this,就相当于锁的是 MessageQueue  相当于 调用同一个MessageQueue的对象是互斥的
3. 一个线程持有一个Looper,一个Looper持有一个MessageQueue
所以:主线程只有一个MessageQueue子线程 发送消息的时候,子线程一次只能处理一个消息,其他的消息需要等锁,这样就保证了不管有多少子线程发送消息 主线程的MessageQueue时刻始终只处理一个消息的入队    

next() 方法中 同样通过synchronized (this) {} 锁住了按照时间节点返回消息的关键代码

既然这里都是一直要放回当前队列的时间最靠前的msg(头消息),加锁的意义在哪里?

这里就是 锁this的魅力 锁住了MessgaeQueue,锁的范围是所有 this正在访问的代码块都会有保护作用,即代表next方法和enqueueMessage方法能够实现互斥统一时间MessageQueue只能入队或者出队这样就保证了MessageQueue的有序性。  

HandlerThread

首先我们看下下面这段代码创建Handler 

Thread thread = new Thread(new Runnable() {Looper looper;
@Override
public void run() {Looper.prepare();looper =Looper.myLooper();Looper.loop();
}
public Looper getLooper() {return looper;
}
});thread.start();Handler handler = new Handler(thread.getLooper());

子线程去获取Looper对象然后通过子线程拿到Looper,这段代码看似么有问题其实有雷运行时可能是出现new Hanlder 中参数Looper 为空

1.可能在执行new Handler对象时子线程没有走完导致looper没有赋值完成
解决:thread.start 后延时去new Handler 从而保证looper不为空,但此时线程依旧是不安全的

看看Goolge是如何解决子线程获取Looper 且线程安全?

public class HandlerThread extends Thread {Looper mLooper;@Overridepublic void run() {mTid = Process.myTid();Looper.prepare();synchronized (this) {mLooper = Looper.myLooper();notifyAll();}Process.setThreadPriority(mPriority);onLooperPrepared();Looper.loop();mTid = -1;}public Looper getLooper() {if (!isAlive()) {return null;}// If the thread has been started, wait until the looper has been created.synchronized (this) {while (isAlive() && mLooper == null) {try {wait();} catch (InterruptedException e) {}}}return mLooper;}

1.HandlerThread 是Thread的子类 new HandlerThread  start 执行其run方法

 prepare 后通过获取Looper关键代码
 synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }

getLooper关键代码
synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
1.通过synchronized (this)  实现了run和getLooper的互斥
2.两种情况 
2.1 :run 先拿到锁

        synchronized锁住的代码先执行,完完全全拿到Looper后通过notifyAll()通知所有 HandlerThread 对象 结束等锁,准备拿锁 ,然后释放锁 
2.2:getLooper  先拿到锁
此时当线程活着时mLooper肯定为空线程执行wait() 等待且释放锁,getLooper  释放锁的同事run方法就会持有锁,因为HandlerThread 就这两个方法会获取锁。
如果是sleep()  线程会阻塞 不会释放锁。

此类写法就保证了不管什么样的情况下当你通过HandlerThread 去getLooper是一定能获取到线程的唯一Looper.此时线程是安全的。             

消息机制之同步屏障
揭秘 Android 消息机制之同步屏障:target==null ?我们知道,Android的消息机制就是Handle - 掘金


 



 

 

  

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

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

相关文章

day9 串口通信

1串口通信串口通信是嵌入式系统和电子设备中最常用的 异步串行通信 方式,核心是通过 TX(发送) 和 R(接收) 两根线实现全双工数据传输。2通信协议分类2.1同步/异步通信同步和异步是串行通信中两种根本不同的数据传输方式…

面向对象的设计模式

一、设计模式简介1、什么是设计模式针对反复出现的问题所总结归纳出的通用解决方设计模式是指在软件开发过程中案。这些方案是众多软件开发人员经过大量实践总结出来的,具有高效性、可维护性和可扩展性等优点。使用设计模式可以帮助开发者更高效地构建软件系统&…

每日钉钉API探索:chooseDepartments专注于部门维度的选择接口

在企业级应用开发过程中,针对组织架构中的部门进行操作是非常常见的需求。今天我们要介绍的是钉钉的chooseDepartments API,它允许用户以部门为单位进行选择,并返回所选部门的相关信息。📌 功能概述chooseDepartments API主要用于…

生产环境CI/CD流水线构建与优化实践指南

生产环境CI/CD流水线构建与优化实践指南 目录 业务场景描述技术选型过程实现方案详解 流水线结构设计并行构建与缓存策略部署策略:滚动、蓝绿、金丝雀回滚与告警自动化 踩过的坑与解决方案总结与最佳实践 业务场景描述 某大型电商平台,为了保证代码持续交…

腾讯云和火山云优劣势对比

从问题本身看,用户没有限定具体场景,说明可能需要一个全面的横向对比。不过云计算服务涉及面太广,我最好先搭建一个框架性的分析结构,再填充具体细节。 首先想到从几个核心维度切入:基础能力(计算存储网络&…

Augment AI 0.502.0版本深度解析:Task、Guidelines、Memory三大核心功能实战指南

Augment AI 0.502.0版本深度解析:Task、Guidelines、Memory三大核心功能实战指南 augment最新版辅助功能全解析续杯免费额度再用满教程|memory|userguidlines|tasksaugment最新插件功能教程前言 在AI辅助编程领域,Augment AI作为一款强大的VS Code插件&…

docker搭建、小皮面板搭建、bp使用、msf

docker搭建Vulhub靶场 docker安装 apt-get install docker.io docker-compose#设置docker代理:创建文件夹以及对应的文件 mkdir /etc/systemd/system/docker.service.d#在该文件中配置自己的代理ip以及代理端口 vim /etc/systemd/system/docker.service.d/http-p…

AI优化器美国VPS集成:智能算力部署与性能调优指南

在当今数字化浪潮中,AI优化器与高性能VPS的融合正成为企业技术架构的核心竞争力。本文将深入解析美国VPS服务器如何通过AI驱动的智能优化技术实现算力突破,从资源配置算法到实时流量调度,全面揭示这种创新组合在跨境电商、大数据分析等场景中…

【保姆级图文详解】Spring AI 中的工具调用原理解析,工具开发:文件操作、联网搜索、网页抓取、资源下载、PDF生成、工具集中注册

目录前言一、Spring AI 中的工具调用(Tool Calling)1.1、概念1.2、工作原理1.3、技术选型1.4、原理解析1.4.1、实现接口1.4.2、工具调用二、工具调用(Tool Calling)开发2.1、文件操作2.1.1、概念描述2.1.2、概念描述2.2、联网搜索…

Redis客户端使用(Client、Java、SpringBoot)

上篇文章: Redis数据类型之zsethttps://blog.csdn.net/sniper_fandc/article/details/149139955?fromshareblogdetail&sharetypeblogdetail&sharerId149139955&sharereferPC&sharesourcesniper_fandc&sharefromfrom_link 目录 1 Redis客户端…

Modbus 开发工具实战:ModScan32 与 Wireshark 抓包分析(一

引言 ** 在工业自动化领域,Modbus 协议犹如一座桥梁,连接着各种电子设备,实现它们之间高效的数据交互。从可编程逻辑控制器(PLC)到人机界面(HMI),再到各类智能传感器,M…

Oracle SQL - 使用行转列PIVOT减少表重复扫描(实例)

[13/JUL/2025, Yusuf Leo, Oracle SQL Performance Tuning Series]我们经常会遇到从同一表中按不同维度取出不同区间的数据,再以相同的属性将这些数据分别汇总到一起的需求。这类需求往往迫使我们对同一个表反复去扫描,当原始数据量太大的时候&#xff0…

HTTP 请求方法详解:GET、POST、PUT、DELETE 等

在 HTTP 协议中,请求方法(也称为 HTTP 动词)定义了客户端希望对指定资源执行的操作类型。这些方法是 HTTP 报文的核心组成部分,决定了请求的目的和行为。 主要 HTTP 请求方法 1. GET 用途:获取资源 特点&#xff1a…

Android 代码热度统计(概述)

1. 前言 代码热度统计,在测试中一般也叫做代码覆盖率。一般得到代码覆盖率后就能了解整体样本在线上的代码使用情况,为无用代码下线提供依据。 做了一下调研,在Android中一般比较常用的是:JaCoCO覆盖率统计工具,它采…

RAG优化

RAG搭建本地AI知识库,在使用过程中遇到的三大痛点,以及相应的进阶方案。1. RAG知识库的三大痛点-- 内容理解不足:AI难以全面理解导入资料的内容,比如在向量编码时候,生硬的截断等导致分析结果不理想。eg: 知识库分割器…

Ubuntu 24.04 启用 root 图形登录

关键词:Ubuntu 24.04、root 登录、GDM、SSH、nano、配置文件一、前言 Ubuntu 默认禁用 root 账户 的图形与 SSH 登录,这是为了安全。但在某些场景(如测试、救援、自动化脚本)你可能需要 直接用 root 登录 GNOME 桌面。本文以 Ubun…

Jekyll + Chirpy + GitHub Pages 搭建博客

Chirpy 是适用于技术写作的简约、响应迅速且功能丰富的 Jekyll 主题,文档地址:https://chirpy.cotes.page/ ,Github 地址:jekyll-theme-chirpy 。 1.开始 打开 chirpy-starter 仓库,点击按钮 Use this template -->…

学习 Flutter (一)

学习 Flutter (一) 1. 引言 什么是 Flutter? Flutter 是 Google 开发的一套开源 UI 框架,主要用于构建高性能、高保真、跨平台的应用程序。使用一套 Dart 编写的代码,开发者可以同时构建适用于: Android iOS Web Windows、mac…

Spring Boot 实现图片防盗链:Referer 校验与 Token 签名校验完整指南

Spring Boot 实现图片防盗链教程(Referer 校验 Token 签名校验)本文将详细讲解两种防盗链实现方案,并提供完整代码示例。方案一:Referer 校验通过检查 HTTP 请求头中的 Referer 字段判断来源是否合法。实现步骤创建 Referer 拦截…

从 JSON 到 Python 对象:一次通透的序列化与反序列化之旅

目录 一、为什么要谈 JSON 二、最快速上手:两把钥匙 dumps 与 loads 三、深入 dumps:参数是魔法棒 四、深入 loads:把风险挡在门外 五、文件级序列化:dump 与 load 六、处理中文与编码陷阱 七、异常场景与调试技巧 八、实…