android-handlerThread

记住一点Handler是子线程到主线程,HandlerThread是主线程到子线程通信

一、HandlerThread简介

HandlerThread是一个轻量级的异步类,可以实现多线程,并且可以实现线程间的通信(HandlerThread主要应用是实现主线程到子线程的通信,子线程到主线程通信可以通过Handler机制)

二、HandlerThread原理

既然已经有Handler可以实现线程间通信,为什么又设计了HandlerThread?

HandlerThread通过字面意思我们可以看到,它是Handler+Thread,那么我们猜测它应该实现了Handler和Thread功能,到底是不是呢,我们向下看。

首先,HandlerThread继承了Thread类,也就是说HandlerThread可以创建一个新的线程。

其次,HandlerThread内封装了Handler类,并自动创建了Looper和MessageQueue,也就是说我们使用HandlerThread线程时,不需要手动创建Looper。

优点:

之前我们使用Handler时,必须自己创建线程,并且自己创建Looper等相关的功能,而HandlerThread则提供了一种方便的方式,相当于内部已经集成了这些功能,不需要我们再手动创建Looper。

三、HandlerThread使用实例

使用步骤:

使用步骤:
//1、创建HandlerThread对象,即创建了一个新的线程,参数为线程名,仅仅是标记线程用的
     HandlerThread mHandlerThread = new HandlerThread("mHandlerThread");
//2、开启线程,第一步创建了一个新的线程,此处开启线程。
    mHandlerThread.start();
//3、创建Handler,并重写handleMessage()方法
    //new Handler(mHandlerThread.getLooper()),即把该Handler绑定到mHandlerThread线程的Looper,
    //进而绑定到了线程mHandlerThread
     Handler mHandlerInHandlerThread = new Handler(mHandlerThread.getLooper()){
            @Override
            public void handleMessage(Message msg) { 这个是在子线程
             //对信息的相关处理操作               
                //在子线程mHandlerThread中运行   
                super.handleMessage(msg);
            }
        };
//4、创建消息并发送消息
    //在主线程中
    Message msg = Message.obtain();
    //主线程向子线程mHandlerThread发送消息通信
    mHandlerInHandlerThread.sendMessage(msg);
//5、结束线程。之前开启线程,当工作结束不再使用该线程时,应该结束该线程
    //即停止了线程的消息循环
    mHandlerThread.quit();
通过使用步骤我们可以看到,HandlerThread实现的功能主要就是主线程向子线程通信,另外可以在使用Handler实现子线程到主线程的通信,进而就可以实现主线程到子线程间的双向通信.

使用实例:

使用实例:
//功能介绍:点击按钮实现延时操作,延时时间到后更新UI。
public class MainActivity extends AppCompatActivity {
     TextView mTxtShowTest;
     Button  mBtnInnerClass ,mBtnHandlerThread, mBtnQuit;
 
    //1、创建HandlerThread对象,即创建了一个新的线程,参数为线程名,仅仅是标记线程用的
    HandlerThread mHandlerThread = new HandlerThread("mHandlerThread");
 
    //匿名内部类,用于子线程向主线程通信用
    private Handler mhandlerInnerClass =  new Handler(){
        @Override
        public void handleMessage(Message msg) {
          super.handleMessage(msg);
            switch (msg.what){
                case 1:
                    //执行的UI操作
                    mTxtShowTest.setText("点击了mBtnInnerClass");
                    break;
                case 2:
                    //执行的UI操作
                    mTxtShowTest.setText("来自于mHandlerInHandlerThread的请求更新");
                    break;
            }
        }
    };
   @Override
   protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        mTxtShowTest = (TextView) findViewById(R.id.mTxtShowTest); 
        mBtnInnerClass = (Button) findViewById(R.id.mBtnInnerClass); 
        mBtnHandlerThread = (Button) findViewById(R.id.mBtnHandlerThread);
        mBtnQuit = (Button) findViewById(R.id.mBtnQuit);
 
        //2、开启线程,第一步创建了一个新的线程,此处开启线程。
         mHandlerThread.start();
 
        //3、创建Handler,并重写handleMessage()方法
        //new Handler(mHandlerThread.getLooper()),即把该Handler绑定
        //到了mHandlerThread线程的Looper,进而绑定到了线程mHandlerThread
        final Handler mHandlerInHandlerThread = new Handler(mHandlerThread.getLooper()){
                    @Override
            public void handleMessage(Message msg) {
               //已经是在子线程mHandlerThread中运行了
                //可以进行一些耗时等操作
                try{
                     Thread.sleep(10000);  //延时操作
                }catch (Exception e){
                    e.getMessage();
                }
 
                //子线程向主线程通信
                Message msg1 = Message.obtain(); 
         msg1.what = 2;
                mhandlerInnerClass.sendMessage(msg1);
 
                super.handleMessage(msg);
            }
         };
 
         mBtnInnerClass.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() { 
                        Message msg = Message.obtain(); 
                        msg.what = 1;
                        mhandlerInnerClass.sendMessage(msg);
                    } 
                }).start();
            }
         });
 
         mBtnHandlerThread.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //4、创建消息并发送消息
                //主线程中
                Message msg = Message.obtain();
                //主线程向子线程发送信息,通信
                mHandlerInHandlerThread.sendMessage(msg);
            }
        });
 
        mBtnQuit.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                  //5、结束线程。之前开启线程,当工作结束不再使用该线程时,应该结束该线程
                  //即停止了线程的消息循环
                    mHandlerThread.quit();
            }
        });
    }
}
注意:

当连续多次点击按钮mBtnHandlerThread是,mTxtShowTest中显示,并不是同时显示的,而是先显示第一次,间隔延时10s(这个10s是在复写handleMessage()方法时自己写的 Thread.sleep(10000))后显示第二次,再间隔延时的10s显示第三次。当我们连续多次点击mBtnHandlerThread按钮时,消息入队列,取出消息执行时,有一个延时10s,之后更新UI,这个消息才算执行完成,然后从消息队列中取出下一个消息,同样有一个延时10s,之后才是更新UI,所以即便时快速连续多次点击按钮,但是执行时每次都间隔一个延时10s.

总结:

以上就是HandlerThread的使用步骤和使用实例,已经测试过可以使用。首先点击按钮mBtnInnerClass, mTxtShowTest显示"点击了mBtnInnerClass",之后再点击按钮mBtnHandlerThread,十秒之后,mTxtShowTest显示"来自于mHandlerInHandlerThread的请求更新"。多次点击按钮mBtnHandlerThread,然后点击mBtnInnerClass,mTxtShowTest显示"点击了mBtnInnerClass",10s后mTxtShowTest显示"来自于mHandlerInHandlerThread的请求更新",之后再点击mBtnInnerClass,mTxtShowTest显示"点击了mBtnInnerClass",10s后又会mTxtShowTest显示"来自于mHandlerInHandlerThread的请求更新",即可证明前面注意中说明的问题。

四、HandlerThread源码分析

在第三部分使用实例中的使用步骤可以看到,使用HandlerThread大概需要5步,具体如下:

使用步骤:
//1、创建HandlerThread对象,即创建了一个新的线程,参数为线程名,仅仅是标记线程用的
     HandlerThread mHandlerThread = new HandlerThread("mHandlerThread");
//2、开启线程,第一步创建了一个新的线程,此处开启线程。
    mHandlerThread.start();
//3、创建Handler,并重写handleMessage()方法
    //new Handler(mHandlerThread.getLooper()),即把该Handler绑定到mHandlerThread线程的Looper,
    //进而绑定到了线程mHandlerThread
     Handler mHandlerInHandlerThread = new Handler(mHandlerThread.getLooper()){
            @Override
            public void handleMessage(Message msg) {
             //对信息的相关处理操作               
                //在子线程mHandlerThread中运行   
                super.handleMessage(msg);
            }
        };
//4、创建消息并发送消息
    //在主线程中
    Message msg = Message.obtain();
    //主线程向子线程mHandlerThread发送消息通信
    mHandlerInHandlerThread.sendMessage(msg);
//5、结束线程。之前开启线程,当工作结束不再使用该线程时,应该结束该线程
    //即停止了线程的消息循环
    mHandlerThread.quit();
我们根据使用步骤 ,一步一步的去看源码。

步骤1、创建HandlerThread对象

源码分析:HandlerThread mHandlerThread = new HandlerThread("mHandlerThread");
/**
 * Handy class for starting a new thread that has a looper. The looper can then be 
 * used to create handler classes. Note that start() must still be called.
 */
//通过HandlerThread 的定义,其继承Thread ,可知HandlerThread 就是个线程
public class HandlerThread extends Thread {
    int mPriority;//优先级
    int mTid = -1;
    Looper mLooper;//当前线程的Looper
    private @Nullable Handler mHandler;
 
    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
 
   public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
    ...
}
总结:

HandlerThread就是个线程,继承了Thread。创建HandlerThread 时,就相当于创建了一个新的线程,并设置了该线程的优先级。

步骤二、开启线程 mHandlerThread.start();

源码分析:
//Causes this thread to begin execution; the Java Virtual Machine
//calls the run method of this thread.
public synchronized void start() {...}
 
//调用mHandlerThread的start(),最终将调用mHandlerThread的run()方法。
    @Override
    public void run() {
        mTid = Process.myTid();//获取当前线程的ID
        Looper.prepare();//创建Looper和MessageQueue
        //通过锁机制,获取当前线程的Looper对象
        synchronized (this) {
            //获取当前线程的Looper对象
            mLooper = Looper.myLooper();
            //发送通知,已经获取当前线程的Looper对象
            //主要是在第三步创建Handler中的mHandlerThread.getLooper()内使用
            notifyAll();
        }
        Process.setThreadPriority(mPriority);//设置线程的优先级
        //主要是做一些开启消息循环前的准备工作 -->>分析1
        onLooperPrepared();
        //开启消息循环
        Looper.loop();
        mTid = -1;
    }
分析1: onLooperPrepared()
    /**
     * Call back method that can be explicitly overridden if needed to execute some
     * setup before Looper loops.
     */
    protected void onLooperPrepared() {
    }
总结:

第一步创建了线程,第二步开启该线程。开启线程时,会创建Looper和MessageQueue,成功后线程会进入消息循环,不断从消息队列中取出消息并分发消息。

步骤三、创建Handler,并重写handleMessage()方法

使用方法: 
Handler mHandlerInHandlerThread = new Handler(mHandlerThread.getLooper()){
            @Override
            public void handleMessage(Message msg) {
              //对信息的相关处理操作               
                 //在子线程mHandlerThread中运行   
                super.handleMessage(msg);
            }
        };
 
//创建Handler时传入Looper对象,此时Handler就与该Looper绑定,进而与Looper所在的线程也绑定
//获取当前线程(即mHandlerThread线程)的Looper对象
    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 {
                        //等待,直到mHandlerThread的run()方法创建了Looper
                        //并 notifyAll()发送通知
                        wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }
总结:

复写handleMessage()方法,该方法是在子线程mHandlerThread中运行 ,而非主线程,要注意。因为是在子线程中,所以可以进行一些耗时相关的操作。另外,通过Handler机制,可以实现该子线程与主线程的通信,进而更新UI。

步骤四、创建消息并发送消息

 //在主线程中
     Message msg = Message.obtain();
 //主线程向子线程mHandlerThread发送消息通信
     mHandlerInHandlerThread.sendMessage(msg);
 //与Handler中的使用方法一样,就不多做介绍了。
步骤五、结束线程

//之前开启线程,当工作结束不再使用该线程时,应该结束该线程
//即停止了线程的消息循环
//源码分析:mHandlerThread.quit();
    public boolean quit() {
        //获取当前Looper
        Looper looper = getLooper();
        if (looper != null) {
            //调用Looper的quit()方法 -->>分析1
            looper.quit();
            return true;
        }
        return false;
    }
 
//分析1: looper.quit();
    public void quit() {
        //调用MessageQueue的quit()方法 -->>分析2
        mQueue.quit(false);
    }
//通过looper.quit()退出是一种不安全的退出方法,
//还有一种安全的退出方法,即looper.quitSafely();
    public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quitSafely();//-->>分析a
            return true;
        }
        return false;
    }
//分析a looper.quitSafely()的源码实现:
    public void quitSafely() {
        //通过源码,截至到目前可以看出,安全不安全主要是mQueue.quit()方法
        //内的boolean变量是true还是false 
        mQueue.quit(true);// -->>分析2
    }
 
分析2: mQueue.quit(false);
    void quit(boolean safe) { 
        synchronized (this) {
            if (mQuitting) {
                    return;
            }
            mQuitting = true;
 
            if (safe) {//安全的退出方式
                    removeAllFutureMessagesLocked();// -->>分析4(请先看分析3)
            } else {//非安全的退出方式
                    removeAllMessagesLocked();// -->>分析3
            }
 
            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }
 
//分析3、 removeAllMessagesLocked()
    //不管该消息是否在使用,把消息队列中的所有消息都回收
    private void removeAllMessagesLocked() {
        Message p = mMessages;
        while (p != null) {
            Message n = p.next;
            //Recycles a Message that may be in-use.
            //这里,不管Message是否在使用,都回收销毁,
            //所以也就决定了它肯定是不安全的
            p.recycleUnchecked();
            p = n;
        }
        mMessages = null;
    }
 
 
//分析4、 removeAllFutureMessagesLocked()
//首先判断是否有消息再使用中,如果没有就按照分析3的方法全部回收,
//如果有消息正在使用中,这个使用的消息不做回收,它依然能够正常执行完成,
//而其他的所有的没有正在执行的全部回收
    private void removeAllFutureMessagesLocked() {
        //Returns milliseconds since boot, not counting time spent in deep sleep.
        final long now = SystemClock.uptimeMillis();
        Message p = mMessages;
        if (p != null) {
            if (p.when > now) {
               //判断消息队列现在是否正在处理消息,如果没有则直接把所有消息回收
               //通过时间来判定的
               removeAllMessagesLocked();
            } else {
                //有正在处理的消息,则处理完成后再退出
                Message n;
                for (;;) {
                    n = p.next;
                    if (n == null) {
                        return;
                    }
                    if (n.when > now) {
                        break;
                    }
                    p = n;
                }
                p.next = null;
                do {
                    p = n;
                    n = p.next;
                    p.recycleUnchecked();
                } while (n != null);
            }
        }
    }

总结:

所谓的安全与否,关键是对正在使用中的消息如何处理来判断的,如果直接回收消息就是不安全,等待它处理完成就是安全的。

五、总结

本文对HandlerThread做了全部分析,从原理、使用实例,到源码分析,希望对您有帮助。

 
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/haovin/article/details/89787721

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

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

相关文章

用于日常任务的实用 Python 脚本

Python 是一种多功能编程语言,以其简单易读而闻名。它广泛应用于从 Web 开发到数据分析等各个领域。Python 脚本,它们可以通过自动执行常见任务来使您的生活更轻松。 用于日常任务的实用 Python 脚本 1. 使用 Pandas 进行数据分析2. 使用 BeautifulSoup …

服务器怎么被远程桌面连接不上,远程桌面连接不上服务器的问题有效解决方案

远程桌面连接不上服务器是一个极其严重的问题,它可能直接影响到我们的工作效率、数据安全,甚至是整个业务运营的顺畅。因此,这个问题必须得到迅速且有效的解决。 当我们尝试远程桌面连接服务器时,可能会遇到连接不上的情况。这其中…

MFC:初步理解序列化与反序列化(含代码实现)

序列化与反序列化是MFC将对象数据以二进制数据流的形式进行存储和读取的机制,读、写的效率很高。通过序列化与反序列化,可以将程序中对象在内存中数据保存到文件 (磁盘) 或者从文件 (磁盘) 中读取到内存以恢复对象数据,从而实现程序对数据的持…

RxSwift - 实现一个MVVM架构的TableView

文章目录 RxSwift - 实现一个MVVM架构的TableView前沿MVVM架构的Tableview目录结构1、模型(Model)2、视图模型(ViewModel)3、视图(View) 界面效果 RxSwift - 实现一个MVVM架构的TableView 前沿 MVVM架构在…

分享一个实用的MySQL一键巡检脚本

今日分享一个实用的MySQL一键巡检脚本,脚本内容还不是很完善,后续会继续进行优化。大家可以先在测试环境执行,确认执行没问题后可以在生产环境进行操作,问题的可以私信我。 MySQL一键巡检脚本的作用主要是帮助数据库管理员快速且…

redux状态管理用法详解

在React中使用redux,官方要求安装俩个其他插件 - Redux Toolkit 和 react-redux 1.ReduxToolkit (RTK) 官方推荐编写 Redux 逻辑的方式,是一套工具的集合集,简化书写方式 简化 store 的配置方式; 内置 immer 支持…

dubbo复习:(19)dubbo 和spring整合(老古董)

一、服务端依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM…

华为SSH实验

华为SSH实验 实验拓扑&#xff1a; 实验要求&#xff1a;从SSH客户端AR1采用stelnet方式登录到SSH 服务器端。 实验步骤&#xff1a; 1.完成基本配置&#xff08;略&#xff09; sys Enter system view, return user view with CtrlZ. [AR1]sys CLIENT [CLIENT]INT g0/0/0 [C…

ECMAScript 详解:深入理解 JavaScript 的核心标准

ECMAScript 详解&#xff1a;深入理解 JavaScript 的核心标准 如果你是一名前端开发者&#xff0c;或者只是对编程感兴趣&#xff0c;那么你一定听说过 ECMAScript。它是 JavaScript 的标准&#xff0c;是现代 web 开发的基础。那么&#xff0c;究竟什么是 ECMAScript&#xf…

智能网联汽车翻译

智能网联汽车 自动驾驶功能场地试验方法及要求 2022-10-20 10:13:01 ChinaAutoRegs|GB/T 41798-2022英文版翻译 智能网联汽车 自动驾驶功能场地试验方法及要求 Intelligent and connected vehicles——Field testing methods and requirements for automated driving function…

打造你的首个QT 5计算器应用

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言&#xff1a;QT 5的力量与我们的计算器 二、QT 5基础&#xff1a;理解UI设计与文件…

Java多线程(04)—— 保证线程安全的方法与线程安全的集合类

一、CAS 与原子类 1. CAS CAS&#xff08;compare and swap&#xff09;&#xff0c;是一条 cpu 指令&#xff0c;其含义为&#xff1a;CAS(M, A, B); M 表示内存&#xff0c;A 和 B 分别表示一个寄存器&#xff1b;如果 M 的值和 A 的值相同&#xff0c;则把 M 和 B 的值交…

数字IC基础:主要的FPGA厂商

相关阅读 数字IC基础https://blog.csdn.net/weixin_45791458/category_12365795.html?spm1001.2014.3001.5482 Xilinx&#xff08;现已被AMD收购&#xff09; Xilinx, 成立于1984年&#xff0c;是FPGA&#xff08;现场可编程门阵列&#xff09;技术的创始者和市场领导者。该公…

dmdts连接kingbase8报错

dmdts连接kingbase报错 环境介绍1 人大金仓jdbc配置2 dmdts 人大金仓jdbc默认配置3 dmdts 修改jdbc配置4 达梦产品学习使用列表 环境介绍 dts版本 使用dmdts连接kingbase金仓数据库报错 无效的URL 对比jdbc连接串,修改配置解决 1 人大金仓jdbc配置 配置URL模版信息等 类名…

民国漫画杂志《时代漫画》第36期.PDF

时代漫画36.PDF: https://url03.ctfile.com/f/1779803-1248636233-8a4a9d?p9586 (访问密码: 9586) 《时代漫画》的杂志在1934年诞生了&#xff0c;截止1937年6月战争来临被迫停刊共发行了39期。 ps: 资源来源网络!

【高校科研前沿】南大王栋、吴吉春教授团队在深度学习助力水库生态调度和优化管理方面取得新进展,成果以博士生邱如健为一作发表于水环境领域国际权威期刊

1.文章简介 论文名称&#xff1a;Integration of deep learning and improved multi-objective algorithm to optimize reservoir operation for balancing human and downstream ecological needs 第一作者及单位&#xff1a;邱如健&#xff08;博士生 南京大学&#xff09;…

Linux自动挂载服务autofs讲解

1.产生原因 2.配置文件讲解 总结&#xff1a;配置客户端&#xff0c;先构思好要挂载的目录如&#xff1a;/abc/cb 然后在autofs.master中编辑&#xff1a; /abc&#xff08;要挂载的主目录&#xff09; /etc/qwe&#xff08;在这个文件里去找要挂载的副目录&#xff0c;这个名…

MySQL基础索引知识【索引创建删除 | MyISAM InnoDB引擎原理认识】

博客主页&#xff1a;花果山~程序猿-CSDN博客 文章分栏&#xff1a;MySQL之旅_花果山~程序猿的博客-CSDN博客 关注我一起学习&#xff0c;一起进步&#xff0c;一起探索编程的无限可能吧&#xff01;让我们一起努力&#xff0c;一起成长&#xff01; 目录 一&#xff0c;索引用…

JMeter源码解析之NewDriver.java(二)完结

JMeter源码解析之NewDriver.java&#xff08;二&#xff09;完结 NewDriver.java主要作用 JMeter程序入口&#xff1a;JMeter的主类-设置初始类路径和加载程序。 文件路径 路径地址&#xff1a;…\apache-jmeter-5.1\src\core\org\apache\jmeter\NewDriver.java 关于Main内…

8-异常与错误

8-异常与错误 1、简介2、异常处理2.1 抛出异常2.2 捕获异常2.3 匹配顺序 3、异常说明4、构造函数中的异常5、析构函数中的异常6、标准库异常 1、简介 在程序编码过程中难免会出现错误&#xff0c;主要有&#xff1a;语法错误、逻辑错误、功能错误等&#xff0c;当我们面对以上…