当前位置: 首页 > news >正文

Handler通信机制

目标:

1.Handler和Looper什么关系?

一个Looper对应一个MessageQueue,可以多个handler往MessageQueue发送消息。

2.一个线程有几个Handler?

3.Handler内存泄漏的原因?

4.使用Message时如何创建它?

5.子线程维护的Looper, 消息队列无消息时的处理方案是怎么样的?有什么用?

6.为什么主线程可以new Handler?如果想要在子线程new Handler,应该怎么办?

7.线程间通信原理是怎么样的?

8.Looper死循环为什么不会导致应用卡死?

9.既然可以存在多个Handler往MessageQueue中添加数据(发消息的各个Handler可能位于不用的线程),那么它内部是如何保证线程安全的?

一、Handler机制

1.1 Handler是什么?

Handler机制是Android提供的消息通信机制。

它涉及多个关键组件,包括Handler、Looper、MessageQueue和Message,共同协作以实现消息的发送、接收和处理。

Handler:负责发送消息和处理消息。当发出一个消息后,首先进入一个消息队列,发送消息的函数即刻返回,而另一个部分在消息队列中逐一将消息取出,然后对消息进行处理。这种机制通常用来处理相对耗时比较长的操作。
Looper:负责循环读取MessageQueue中的消息,读到消息之后就把消息交给Handler去处理。
MessageQueue:存储消息对象的队列。
Message:消息对象,是Handler机制中传递的基本单位。

1.2 Handler工作原理

Handler机制的工作流程大致如下:

1) 创建Handler:首先需要创建一个Handler对象,其构造函数中的参数如async和callback用于确定消息的处理方式。async参数确定用Handler发送的消息是否要设置成异步消息,而callback参数则允许在dispatchMessage回调时优先调用callback中的代码。
2)发送消息:通过调用Handler的sendMessage或post等方法发送消息到MessageQueue中。
3)处理消息:Looper循环读取MessageQueue中的消息,并将它们分发给相应的Handler进行处理。处理过程包括调用Handler的handleMessage方法(如果存在回调,则优先执行回调)。
Handler机制在Android开发中特别重要,因为它允许在主线程中更新UI,同时避免阻塞主线程。通过在子线程中执行耗时操作,并通过Handler将结果发送回主线程进行UI更新,可以实现线程间的有效通信和UI的平滑更新。

二、线程间内存共享

MessageQueue是线程间共享的消息容器,用于管理消息。

完成线程间通信的原理:内存共享,通过共享MessageQueue消息队列,实现主线程和子线程通信。

三、消息Message

3.1 消息排序

按照msg.updateTime进行排序,相对于系统开机时间的时间戳。

3.2 消息创建

消息创建采用享元模式,增加消息对象复用。

1)消息存在一个缓冲池sPool(大小为50个),缓存使用完的消息对象;

2)创建消息的时候,优先从sPool中拿取一个消息对象;

3)消息使用完成后,将消息字段重置,然后添加到sPool,用于复用。

四、Handler内存泄漏和解决方案

4.1 Handler内存泄漏的原因

内存泄漏原因:JVM回收的时候问题,根可达算法,被JVM GC Roots直接或者间接引用的对象不能够被回收。

Activity中,

Handler handler = new Handler();

handler是一个匿名内部类,持有Activity的this对象;

ActivityThread的

static sMainLooper: 是一个静态变量,作为GC Roots

static sThreadLocal->Looper-->MessageQueue-->Message-->Handler-->Activity

msg.target = this;(Handler)

采用delay发送的消息,引用链仍然存在。

4.2 解决内存泄漏的方法

打断引用链。

1.Handler声明为静态内部类对象,不持有Activity引用

2.Handler采用弱引用Activity对象

Looper执行的动力:线程提供的。

EventBus、Retrofit与主线程通信,都需要借助Handler

五、Handler如何正确创建

5.1 主线程

主线程为什么创建Handler不需要指定Looper?

Handler Handler = new Handler();

主线程main运行的时候,从主线程Looper.mainLooper()

5.2 子线程撞见

1)先创建Looper

创建以后,调用Looper.prepare()

Looper.prepare

2) 初始化Handler

3)Looper.loop()

5.3 子线程Handler  HandlerThread

封装了Handler

HandlerThread获取Looper

public Looper getLooper() {if (!isAlive()) {return null;}boolean wasInterrupted = false;// If the thread has been started, wait until the looper has been created.synchronized (this) {while (isAlive() && mLooper == null) {try {wait();} catch (InterruptedException e) {wasInterrupted = true;}}}/** We may need to restore the thread's interrupted flag, because it may* have been cleared above since we eat InterruptedExceptions*/if (wasInterrupted) {Thread.currentThread().interrupt();}return mLooper;}

为什么需要采用while

因为如果是别的线程唤醒,如果Looper还没有初始化,需要继续等待Looper初始化。因此需要采用while。直到Looper有值。 

wait挂起:挂起线程,并且释放锁

sleep: 

六、Handler多线程安全

消息入列

消息获取:MessageQueue.next()  拿取一个消息

多个Handler往MessageQueue发送消息,如何保证线程安全性。

6.1 存消息的时候

boolean MessageQueue.enqueueMessage(Message msg, long when)

采用加锁的方式

synchronized (this) {

6.2 取消息的时候

Message MessageQueue.next()

synchronized (this) {

存消息和取消息的时候,同步进行。

6.3 Loop与ThreadLocal

    /*** Return the Looper object associated with the current thread.  Returns* null if the calling thread is not associated with a Looper.*/public static @Nullable Looper myLooper() {return sThreadLocal.get();}

Looper通过ThreadLocal进行缓存。

每个线程对应于自己的Looper。 

七、同步消息和异步消息

消息屏障:target==null的消息。保证异步消息优先执行(UI刷新优先)

异步消息:isAync=true是异步消息,用于UI刷新。view.scheduleTraversals

同步消息:

添加消息屏障:

postSyncBarrier: 插入一个消息屏障

removeSyncBarrier: 移除消息屏障

丢帧;

skip 30 frames!

八、Looper.loop死循环为什么不会发生ANR问题

等待和休眠

1)没有消息的时候,阻塞等待消息到来;

2)等待消息执行时间:没有到消息可执行时间,epoll机制进行超时等待。

8.1 epoll机制

涉及到I/O

select: 非租塞忙轮询方式。没有数据的会出现CPU空转,浪费资源。直到一系列I/O事件存在,但不知道是哪几个流存在事件。

epoll_wait: 有N个I/O事件,一个线程处理多个I/O事件。没有I/O事件的时候进行阻塞,

相关文章:

  • [论文笔记]Mixtral of Experts
  • 新版FMEA培训的应用误区是如何产生的?
  • XML解析库tinyxml2库使用详解
  • Windows系统安装Docker环境详细教程
  • Armbian OS(基于ubuntu24) 源码编译mysql 5.7
  • 路径规划 | 图解遗传(GA)算法(附ROS C++仿真)
  • 传神论文中心|第11期人工智能领域论文推荐
  • RPG Maker MZ中被你忽略的干货操作——独立开关和“开关”在事件页中的关系
  • Web前端魂斗罗:深度剖析前端技术的奇幻之旅
  • flutter实现UDP发送魔法包唤醒主机
  • 碳素钢化学成分分析 螺纹钢材质鉴定 钢材维氏硬度检测
  • 【Unity回调函数】创建自己的外部回调函数——以按钮点击为例
  • 静态工厂方法替代构造器
  • 【ai】Omniverse 微服务架构及NVIDIA Omniverse™ Launcher
  • 【C语言】32个关键字
  • 【附node操作实例】redis简明入门系列—字符串类型
  • Akka系列(七):Actor持久化之Akka persistence
  • CSS选择器——伪元素选择器之处理父元素高度及外边距溢出
  • Flex布局到底解决了什么问题
  • idea + plantuml 画流程图
  • MySQL主从复制读写分离及奇怪的问题
  • nfs客户端进程变D,延伸linux的lock
  • React as a UI Runtime(五、列表)
  • 大整数乘法-表格法
  • 翻译--Thinking in React
  • 关于 Linux 进程的 UID、EUID、GID 和 EGID
  • 浏览器缓存机制分析
  • 微信支付JSAPI,实测!终极方案
  • 想晋级高级工程师只知道表面是不够的!Git内部原理介绍
  • 新书推荐|Windows黑客编程技术详解
  • 7行Python代码的人脸识别
  • 分布式关系型数据库服务 DRDS 支持显示的 Prepare 及逻辑库锁功能等多项能力 ...
  • #VERDI# 关于如何查看FSM状态机的方法
  • $redis-setphp_redis Set命令,php操作Redis Set函数介绍
  • (1) caustics\
  • (6)STL算法之转换
  • (C++17) std算法之执行策略 execution
  • (DFS + 剪枝)【洛谷P1731】 [NOI1999] 生日蛋糕
  • (Qt) 默认QtWidget应用包含什么?
  • (Redis使用系列) SpringBoot 中对应2.0.x版本的Redis配置 一
  • (附源码)springboot金融新闻信息服务系统 毕业设计651450
  • (一)、python程序--模拟电脑鼠走迷宫
  • (转)负载均衡,回话保持,cookie
  • ***检测工具之RKHunter AIDE
  • ..回顾17,展望18
  • .NET COER+CONSUL微服务项目在CENTOS环境下的部署实践
  • .NET 设计一套高性能的弱事件机制
  • .net和jar包windows服务部署
  • @GlobalLock注解作用与原理解析
  • @requestBody写与不写的情况
  • @vue-office/excel 解决移动端预览excel文件触发软键盘
  • []使用 Tortoise SVN 创建 Externals 外部引用目录
  • [000-01-022].第06节:RabbitMQ中的交换机介绍
  • [2016.7 Day.4] T1 游戏 [正解:二分图 偏解:奇葩贪心+模拟?(不知如何称呼不过居然比std还快)]
  • [2018/11/18] Java数据结构(2) 简单排序 冒泡排序 选择排序 插入排序