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;
    @Override
    public void run() {
        Looper.prepare();
        looper = Looper.myLooper();
        Looper.loop();
    }

    public Looper getLooper() {
        return looper;
    }
});
threadA.start();

Handler workHandler = new Handler(threadA.getLooper()) {
    @Override
     public 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);
    }

    @Override
    public 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;
    }

    @Override
    public 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;
    }

    @Override
    public 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() {
    }

    @Override
    public 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的第五篇文章了,相信这五篇文章的内容足以解决你工作和面试中的问题。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

liunx网络套接字 | 实现基于tcp协议的echo服务

前言:本节讲述linux网络下的tcp协议套接字相关内容。博主以实现tcp服务为主线,穿插一些小知识点。以先粗略实现,后精雕细琢为思路讲述实现服务的过程。下面开始我们的学习吧。 ps:本节内容建议了解网络端口号的友友们观看哦。 目录…

【果蔬识别】Python+卷积神经网络算法+深度学习+人工智能+机器学习+TensorFlow+计算机课设项目+算法模型

一、介绍 果蔬识别系统,本系统使用Python作为主要开发语言,通过收集了12种常见的水果和蔬菜(‘土豆’, ‘圣女果’, ‘大白菜’, ‘大葱’, ‘梨’, ‘胡萝卜’, ‘芒果’, ‘苹果’, ‘西红柿’, ‘韭菜’, ‘香蕉’, ‘黄瓜’)…

isp框架代码理解

一、整体框架如下: 1 外层的src中 1.1 从camera.c->task.c:封装了3层,透传到某个功能的本级。 1.2 core.c和capability.c中实现:开机初始化加载参数。2. plat/src中 2.1 fun.c中继task.c又透传了一层;以及最后功能…

VuePress文档初始化请求过多问题探讨

1. 背景 公司有部门使用VuePress 1.0搭建平台的帮助文档,前期文档不是很多,所以没有暴露出特别明显的问题。但随着文档的逐步迭代和内容的增多,出现了大量的并发请求,总共有218个请求,导致服务器带宽耗尽、响应速度下降…

Paimon x StarRocks 助力喜马拉雅构建实时湖仓

作者:王琛 喜马拉雅数仓专家 小编导读: 本文将介绍喜马拉雅直播的业务现状及数据仓库架构的迭代升级,重点分享基于 Flink Paimon StarRocks 实现实时湖仓的架构及其成效。我们通过分钟级别的收入监控、实时榜单生成、流量监测和盈亏预警&am…

Virtuoso使用layout绘制版图、使用Calibre验证DRC和LVS

1 绘制版图 1.1 进入Layout XL 绘制好Schmatic后,在原理图界面点击Launch,点击Layout XL进入版图绘制界面。 1.2 导入元件 1、在Layout XL界面左下角找到Generate All from Source。 2、在Generate Layout界面,选中“Instance”&#…

vscode插件-08 Golang

文章目录 Go安装其他必须软件 Go Go语言环境,只需安装这一个插件。然后通过vscode命令下载安装其他go环境需要的内容。 程序调试,需要创建.vscode文件夹并编写launch.json文件。 安装其他必须软件 ctrlshiftp,调出命令面板,输入…

Linux系列-vim的使用

🌈个人主页:羽晨同学 💫个人格言:“成为自己未来的主人~” vim的使用 vim是多模式编辑器,不同的是vim是vi的升级版本,它不仅兼容vi的所有指令,而且还有一些新的特性在里面,比如语法加亮&am…

强化学习DQN实践(gymnasium+pytorch)

Pytorch官方教程中有强化学习教程,但是很多中文翻译都太老了,里面的代码也不能跑了 这篇blog按照官方最新教程实现,并加入了一些个人理解 工具 gymnasium:由gym升级而来,官方定义:An API standard for rei…

2024快手面试算法题-生气传染

问题描述 思路分析 生气只会向后传播,最后一个生气的人一定是最长连续没有生气的人中的最后一个人,前提是前面得有一个人生气。 注意,一次只能传播一个人,比如示例1,第一次只会传播给第一个P,不会传播给第…

入门 | Kafka数据使用vector消费到Loki中使用grafana展示

一、Loki的基本介绍 1、基本介绍 Loki 是由 Grafana Labs 开发的一款水平可扩展、高性价比的日志聚合系统。它的设计初衷是为了有效地处理和存储大量的日志数据,与 Grafana 生态系统紧密集成,方便用户在 Grafana 中对日志进行查询和可视化操作。 从架构…

分类算法——逻辑回归 详解

逻辑回归(Logistic Regression)是一种广泛使用的分类算法,特别适用于二分类问题。尽管名字中有“回归”二字,逻辑回归实际上是一种分类方法。下面将从底层原理、数学模型、优化方法以及源代码层面详细解析逻辑回归。 1. 基本原理 …

【Spring MVC】DispatcherServlet 请求处理流程

一、 请求处理 Spring MVC 是 Spring 框架的一部分,用于构建 Web 应用程序。它遵循 MVC(Model-View-Controller)设计模式,将应用程序分为模型(Model)、**视图(View)和控制器&#x…

现代数字信号处理I--最佳线性无偏估计 BLUE 学习笔记

目录 1. 最佳线性无偏估计的由来 2. 简单线性模型下一维参数的BLUE 3. 一般线性模型下一维参数的BLUE 4. 一般线性模型下多维参数的BLUE 4.1 以一维情况说明Rao论文中的结论 4.2 矢量参数是MVUE的本质是矢量参数中的每个一维参数都是MVUE 4.3 一般线性模型多维参数BLUE的…

QT(绘图)

目录 QPainter QPainter 的一些关键步骤和使用方法: QPainter 的一些常用接口: 1. 基础绘制接口 2. 颜色和画刷设置 3. 图像绘制 4. 文本绘制 5. 变换操作 6. 渲染设置 7. 状态保存与恢复 8. 其它绘制方法 示例代码1: 示例代码…

【js逆向学习】某多多anti_content逆向(补环境)

文章目录 声明逆向目标逆向分析逆向过程总结 声明 本文章中所有内容仅供学习交流使用,不用于其他任何目的,不提供完整代码,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的…

【安全解决方案】深入解析:如何通过CDN获取用户真实IP地址

一、业务场景 某大型互联网以及电商公司为了防止客户端获取到真实的ip地址,以及达到保护后端业务服务器不被网站攻击,同时又可以让公安要求留存网站日志和排查违法行为,以及打击犯罪的时候,获取不到真实的ip地址,发现…

Java | Leetcode Java题解之第524题通过删除字母匹配到字典里最长单词

题目&#xff1a; 题解&#xff1a; class Solution {public String findLongestWord(String s, List<String> dictionary) {int m s.length();int[][] f new int[m 1][26];Arrays.fill(f[m], m);for (int i m - 1; i > 0; --i) {for (int j 0; j < 26; j) {…

python爬虫抓取豆瓣数据教程

环境准备 在开始之前&#xff0c;你需要确保你的Python环境已经安装了以下库&#xff1a; requests&#xff1a;用于发送HTTP请求。BeautifulSoup&#xff1a;用于解析HTML文档。 如果你还没有安装这些库&#xff0c;可以通过以下命令安装&#xff1a; pip install requests…

Python实现深度学习模型预测控制(tensorflow)DL-MPC(Deep Learning Model Predictive Control

链接&#xff1a;深度学习模型预测控制 &#xff08;如果认为有用&#xff0c;动动小手为我点亮github小星星哦&#xff09;&#xff0c;持续更新中…… 链接&#xff1a;WangXiaoMingo/TensorDL-MPC&#xff1a;DL-MPC&#xff08;深度学习模型预测控制&#xff09;是基于 P…