Android Handler消息机制(五)-HandlerThread完全解析

Android 消息机制Handler完全解析(一)

Android 消息机制Handler完全解析(二)

Android Handler消息机制-消息屏障(三)

Android Handler消息机制完全解析(四)-IdleHandler和epoll机制

Android Handler消息机制(五)-HandlerThread完全解析

关于Handler的相关文章我们已经学习了四篇了,这一篇我们来学习下另一个Handler相关的知识HandlerThread,从名字来看它有Handler和Thread应该跟这两者有关,同样我们还是带着问题来学习今天的内容

  • 为什么要有HandlerThread
  • HandlerThread解决了什么问题

首先Handler是可以跨线程进行通讯的,大家想象一个场景:有两个Thread,ThreadA和ThreadB,如何在ThreadB中声明一个Handler对象且ThreadB中的Handler发送消息会发送到ThreadA中处理?通过前面的学习我们知道在Handler有一个传递Looper对象的构造方法

public Handler(@NonNull Looper looper) {this(looper, null, false);
}

因此我们可以在ThreadB中获取到ThreadA中的looper对象,然后声明Handler的时候将此looper传递给Handler对象,这样就可以达到在ThreadB中声明的Handler发送的消息会发送到ThreadA中处理。来看一段代码

Thread threadA = new Thread(new Runnable() {Looper looper;@Overridepublic void run() {Looper.prepare();looper = Looper.myLooper();Looper.loop();}public Looper getLooper() {return looper;}
});
threadA.start();Handler workHandler = new Handler(threadA.getLooper()) {@Overridepublic void handleMessage(@NonNull Message msg) {// dosomething}};

如上所示第16行在主线程声明一个Handler的时候传递了threadA的Looper,这样通过workHandler发送的消息就能在threadA中收到,但是这样有一个问题就是上述threadA.getLooper()这个方法获取不到,因为在实例化handler的时候的Runnable是一个匿名内部类,而外部类不能直接访问匿名内部类的属性和方法,那么怎么办呢?我们可以将将Thread封装一下

public class MyHandlerThread extends Thread {Looper looper;MyHandlerThread(String name) {super(name);}@Overridepublic void run() {super.run();Looper.prepare();looper = Looper.myLooper();Looper.loop();}public Looper getLooper() {return looper;}
}

于是我们调用就可以像下面这样调用

MyHandlerThread threadA = new MyHandlerThread("threadTest");
threadA.start();
Handler workHandler = new Handler(threadA.getLooper());

这样封装之后似乎在线程中声明一个Handler简洁了很多,因为我们把Looper的一些操作封装在run方法里。但是这样有并发的问题,在第二行调用threadA.start之后会运行MyHandlerThread的run方法并对looper进行赋值(这个过程是在子线程中执行的),而主线程的代码运行到第三行时会调用threadA.getLooper方法,此时有可能子线程还未对其进行赋值,也就是说此时threadA.getLooper可能为空。怎么解决这种问题呢?鉴于上述一系列问题所以谷歌给我们封装了一个HandlerThread,我们来看看它是怎么解决这个并发问题的呢?也就是说它是怎么确保执行threadA.getLooper时looper一定是有值的。要想彻底弄明白这个问题,需要对多线程并发有一定的了解,首先要理解synchronized关键字,我在这里写两段伪代码

public class Test {public void test1() {synchronized (this) {code1}}public void test2() {synchronized (this) {code2}}
}

问题来了,test1中的code1和test2中的code2是互斥访问的吗?答案是的,因为者两者持有的是同一把锁所以同一时刻只能执行一个。如果是下面这样就不会互斥

public class Test {public void test1() {synchronized (obj1) {code1}}public void test2() {synchronized (obj2) {code2}}
}

了解synchronized还不够,还需要对多线程的wait和notify有所了解,这里我直接上一个实例,这个实例就是利用多线程的wait和notify方法,来达到等待、唤醒的一个操作

public class ThreadA extends Thread {private Object obj;public ThreadA(Object obj) {this.obj = obj;}@Overridepublic void run() {synchronized (obj) {try {System.out.println("wait之前的时间:" + TimeUtils.getDate(System.currentTimeMillis()));obj.wait();System.out.println("wait之后的时间:" + TimeUtils.getDate(System.currentTimeMillis()));} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}

首先定义一个ThreadA它继承自Thread在构造方法中接收一个Object对象用来加锁,并重写run方法,在run方法中调用wait

public class ThreadB extends Thread {private Object obj;public ThreadB(Object obj) {this.obj = obj;}@Overridepublic void run() {synchronized (obj) {System.out.println("notify之前的时间:" + TimeUtils.getDate(System.currentTimeMillis()));obj.notify();System.out.println("notify之后的时间:" + TimeUtils.getDate(System.currentTimeMillis()));}}
}

然后定义一个ThreadB与ThreadA类似,只不过它在run方法里调用notify方法,然后再main函数调用

Object object = new Object();
ThreadA threadA = new ThreadA(object);
threadA.start();Thread.sleep(3000);ThreadB threadB = new ThreadB(object);
threadB.start();

大家先想一想如何打印,打印结果

wait之前的时间:2024-10-29 17:16:09
notify之前的时间:2024-10-29 17:16:12
notify之后的时间:2024-10-29 17:16:12
wait之后的时间:2024-10-29 17:16:12

发现没有在threadA执行到wait方法后就释放了锁并进入了等待状态,后面的代码暂时不执行,等threadB拿到锁并调用notify方法并且将代码块执行完后将锁释放,此时ThreadA中的wait收到了notify的通知,继续后面的代码执行。这种场景在并发编程中也是经常用到,大家要记住这种场景的处理方式即加锁+wait+notify的方式。

回想一下我们刚才遇到的问题即如何确保threadA.getLooper()方法一定有不为null的返回值。这里是不是提供了一个思路:可以对getLooper方法加锁并进行判断如果looper为空则调用wait等待,run方法也加锁当完成了对mLooper的赋值之后notify一下,此时getLooper方法被唤醒并且looper已经完成了赋值。

说了这么多好像HandlerThread还没正式登场,上面我们讲的内容其实就是HandlerThread的主要原理,HandlerThread的源码并不多总共还不到200行,我精简一下

public class HandlerThread extends Thread {...Looper mLooper;private @Nullable Handler mHandler;/*** Call back method that can be explicitly overridden if needed to execute some* setup before Looper loops.*/protected void onLooperPrepared() {}@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;}...
}

可以看到首先HandlerThread是一个线程类它继承自Thread,并且对其进行了封装,在run方法里调用了Looper.prepare()和Looper.loop()方法,第17行和33行的锁是不是就跟我们上面讲的实例是一样,在调用getLooper方法时会判断mLooper是否为null,如果mLooper为null说明run方法里还未对mLooper赋值,此时调用wait方法进入等待状态且getLooper方法会将锁释放,run方法拿到锁并对mLooper赋值之后调用notifyAll方法此时getLooper收到这个通知,此时会再次判断mLooper是否为空,由于此时在run方法里已经对mLooper进行了赋值因此此时mLooper是不为空的,所以会执行return mLooper,这样getLooper就确保了mLooper一定有值。

关于HandlerThread的源码以及封装的过程就讲到这里,这也是关于Handler的第五篇文章了,相信这五篇文章的内容足以解决你工作和面试中的问题。

如果大家有疑问或者发现错误,欢迎在下方留言,我会在第一时间回答!!
如果觉得文章对你有帮助就帮忙点个赞吧。

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

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

相关文章

PPT制作新选择:本地部署PPTist结合内网穿透实现实时协作和远程使用

文章目录 前言1. 本地安装PPTist2. PPTist 使用介绍3. 安装Cpolar内网穿透4. 配置公网地址5. 配置固定公网地址 💡 推荐 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。【点击跳转到网站】 前…

【GO学习笔记 go基础】编译器下载安装+Go设置代理加速+项目调试+基础语法+go.mod项目配置+接口(interface)

编译器下载&安装 下载并安装go1.23.2.windows-amd64.msi默认安装再C:\Program Files\Go\ PS C:\Users\kingchuxing\Documents> go version go version go1.23.2 windows/amd64Go设置GOPROXY国内加速 windows // 启用 Go Modules 功能 PS C:\Users\kingchuxing…

appium+mumu模拟器+python 嚼碎菜鸟教程

1、android sdk 下载安装 下载地址:https://www.androiddevtools.cn/index.html# 选择版本:android sdk【sdk tools:installer_r24.4.1-windows.exe】 参考步骤:https://blog.csdn.net/2401_83004375/article/details/139300339 2、jdk 安装…

关于我的数据库——MySQL——第二篇

(叠甲:如有侵权请联系,内容都是自己学习的总结,一定不全面,仅当互相交流(轻点骂)我也只是站在巨人肩膀上的一个小卡拉米,已老实,求放过)。 表的操作 创建表…

Python飞舞蝙蝠

目录 系列文章 写在前面 完整代码 代码分析 写在后面 系列文章 序号直达链接爱心系列1Python制作一个无法拒绝的表白界面2Python满屏飘字表白代码3Python无限弹窗满屏表白代码4Python李峋同款可写字版跳动的爱心5Python流星雨代码6Python漂浮爱心代码7Python爱心光波代码…

Canvas简历编辑器-选中绘制与拖拽多选交互设计

Canvas简历编辑器-选中绘制与拖拽多选交互设计 在之前我们聊了聊如何基于Canvas与基本事件组合实现了轻量级DOM,并且在此基础上实现了如何进行管理事件以及多层级渲染的能力设计。那么此时我们就依然在轻量级DOM的基础上,关注于实现选中绘制与拖拽多选交…

基于Multisim的四位抢答器设计与仿真

四位选手进行抢答比赛,用基本门电路及集成逻辑器件构成四人抢答器。选手编号分别为1,2,3,4号,用S1,S2,S3,S4四个按钮作为抢答按钮,S0按钮为总清零按钮。当四人中任何一个…

20241031使用Rockchip原厂RK3566的Buildroot编译RK3399方案

20241031使用Rockchip原厂RK3566的Buildroot编译RK3399方案 2024/10/31 18:06 RK3566_Linux5.10_V1.2.0_20241022.tar.gz rk356x_linux5p10_v120_20241025_1020.tgz 由于Rockchip可能像全志一样,为了简单,直接所有的SDK都打包/放置在一个git仓库里了&…

算法定制LiteAIServer视频智能分析软件的过亮、过暗及抖动检测应用场景

在现代社会中,视频监控系统扮演着举足轻重的角色,其视频质量直接关乎监控系统的可靠性与有效性。算法定制LiteAIServer通过引入抖动检测和过亮过暗检测功能,为视频监控系统的稳定性和用户体验带来了显著提升。 以下是对这两种功能的技术实现、…

OpenCV 学习笔记

OpenCV 环境安装 pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple 显示窗口 cv2.namedWindow 是 OpenCV 库中的一个函数,用于创建一个命名窗口,以便在该窗口中显示图像或进行其他图形操作。这个函数在处理图像和视频时非常…

超越 YOLOv8,MAF-YOLO利用重参化异构卷积大幅度提升多尺度信息融合能力!

由于多尺度特征融合的有效性能,路径聚合FPN(PAFPN)被广泛应用于YOLO检测器中。 然而,它不能同时高效且自适应地融合高级语义信息与低级空间信息。本文提出了一种名为MAF-YOLO的新模型,这是一个具有多功能 Neck 网络的新…

堆(堆排序,TOP K, 优先级队列)

1 概念解释 堆的定义:堆是一颗完全二叉树,分为大堆和小堆 大堆:一棵树中,任何父亲节点都大于等于孩子的节点,大堆的根结点最大 小堆:一棵树中,任何父亲节点都小于等于孩子节点,小堆…

练习LabVIEW第二十八题

学习目标: 刚学了LabVIEW,在网上找了些题,练习一下LabVIEW,有不对不好不足的地方欢迎指正! 第二十八题: 建立一个VI,模拟滚动—个骰子(骰子取值1~6),跟踪骰子滚动后的取值出现次数…

延迟队列的安装步骤

RabbitMQ 中的延迟队列(Delayed Queue)是一种特殊的队列,用于在消息被发送后延迟一段时间再投递给消费者。它在许多场景中非常有用,例如需要定时执行的任务、限流、重试机制等。 使用场景 定时任务: 例如发送提醒邮件或通知&…

六,Linux基础环境搭建(CentOS7)- 安装HBase

Linux基础环境搭建(CentOS7)- 安装HBase 大家注意以下的环境搭建版本号,如果版本不匹配有可能出现问题! 一、HBase下载及安装 HBase是一个分布式的、面向列的开源数据库,该技术来源于 Fay Chang 所撰写的Google论文“…

在 .NET 8 Web API 中实现 Entity Framework 的 Code First 方法

本次介绍分为3篇文章: 1:.Net 8 Web API CRUD 操作.Net 8 Web API CRUD 操作-CSDN博客 2:在 .Net 8 API 中实现 Entity Framework 的 Code First 方法https://blog.csdn.net/hefeng_aspnet/article/details/143229912 3:.NET …

斐波那契时间序列,精准捕捉市场拐点 MT4免费公式源码!

指标名称:斐波那契时间序列 版本:MT4 ver. 2.01 斐波那契时间序列是一种技术分析工具,通过将斐波那契数列(如1, 2, 3, 5, 8, 13等)应用于时间轴上,用于预测市场价格的时间周期拐点。斐波那契时间序列在股…

Unsafe Fileupload-pikachu

系列目录 第一章 暴力破解 第二章 Cross-Site Scripting-pikachu 第三章 CSRF 第四章 sql-injection 第五章 RCE 第六章 File inclusion 第七章 Unsafe filedownload 第八章 Unsafe fileupload 概述 不安全的文件上传漏洞概述 文件上传功能在web应用系统很常见&#x…

嵌入式学习-网络-Day05

嵌入式学习-网络-Day05 1.网络超时检测 1.1应用场景 1.2设置超时检测 1)通过参数设置 2)setsockopt属性设置 3)定时器alarm设置 2.广播 2.1广播发送流程: 2.2广播接收流程: 3.组播 3.1组播发送流程 3.2组播接收流程 4.…

Android启动流程_SystemServer阶段

前言 上一篇文档我们描述了在 Android 启动流程中 Zygote 部分的内容,从 Zygote 的配置、启动、初始化等内容展开,描述了 Zygote 在 Android 启动中的功能逻辑。本篇文档将会继续 Android 启动流程的描述,从 SystemServer 进程的内容展开&am…