Android HandlerThread 详解

Android HandlerThread 详解概述HandlerThread相信大家都比较熟悉了,从名字上看是一个带有Handler消息循环机制的一个线程,比一般的线程多了消息循环的机制,可以说是Handler+Thread的结合

大家好,又见面了,我是你们的朋友全栈君。

概述

HandlerThread 相信大家都比较熟悉了,从名字上看是一个带有 Handler 消息循环机制的一个线程,比一般的线程多了消息循环的机制,可以说是 Handler + Thread 的结合,从源码上看也是如此的设计。

对 Handler 不熟悉的可以看 Android Handler 源码分析(详细) 一文,会教你一步步去认识 Handler 。

一般情况下如果需要子线程和主线程之间相互交互,可以用 HandlerThread 来设计,这比单纯的 Thread 要方便,而且更容易管理,因为大家都知道Thread 的生命周期在一些情况下是不可控制的,比如直接 new Thread().start() 这种方式在项目中是不推荐使用的,实际上 Android 的源码中也有很多地方用到了 HandlerThread,下面我将分析一下 HandlerThread 用法以及源码解析。

使用示例

// 实例对象,参数为线程名字
HandlerThread handlerThread = new HandlerThread("handlerThread");
// 启动线程
handlerThread.start();
// 参数为 HandlerThread 内部的一个 looper
    Handler handler = new Handler(handlerThread.getLooper()) {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

注意:这个使用的顺序是不能更改的!!!,因为如果不先让子线程 start 起来,那么创建主线程的 handler 的参数 getLooper 是获取不到的,这一点可以看源码就清楚。

Demo 详解

这里模拟在子线程下载东西,然后和主线程之间进行通信。主线程知道了下载开始和下载结束的时间,也就能及时改变界面 UI。

首先是 
DownloadThread 类,继承于 
HandlerThread,用于下载。

public class DownloadThread extends HandlerThread{

    private static final String TAG = "DownloadThread";

    public static final int TYPE_START = 2;//通知主线程任务开始
    public static final int TYPE_FINISHED = 3;//通知主线程任务结束

    private Handler mUIHandler;//主线程的Handler

    public DownloadThread(String name) {
        super(name);
    }

    /*
    * 执行初始化任务
    * */
    @Override
    protected void onLooperPrepared() {
        Log.e(TAG, "onLooperPrepared: 1.Download线程开始准备");
        super.onLooperPrepared();
    }

    //注入主线程Handler
    public void setUIHandler(Handler UIhandler) {
        mUIHandler = UIhandler;
        Log.e(TAG, "setUIHandler: 2.主线程的handler传入到Download线程");
    }

    //Download线程开始下载
    public void startDownload() {
        Log.e(TAG, "startDownload: 3.通知主线程,此时Download线程开始下载");
        mUIHandler.sendEmptyMessage(TYPE_START);

        //模拟下载
        Log.e(TAG, "startDownload: 5.Download线程下载中...");
        SystemClock.sleep(2000);

        Log.e(TAG, "startDownload: 6.通知主线程,此时Download线程下载完成");
        mUIHandler.sendEmptyMessage(TYPE_FINISHED);
    }
} 

然后是 MainActivity 部分,UI 和处理消息。

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    private DownloadThread mHandlerThread;//子线程
    private Handler mUIhandler;//主线程的Handler

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //初始化,参数为线程的名字
        mHandlerThread = new DownloadThread("mHandlerThread");
        //调用start方法启动线程
        mHandlerThread.start();
        //初始化Handler,传递 mHandlerThread 内部的一个 looper
        mUIhandler = new Handler(mHandlerThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                //判断mHandlerThread里传来的msg,根据msg进行主页面的UI更改
                switch (msg.what) {
                    case DownloadThread.TYPE_START:
                        //不是在这里更改UI哦,只是说在这个时间,你可以去做更改UI这件事情,改UI还是得在主线程。
                        Log.e(TAG, "4.主线程知道Download线程开始下载了...这时候可以更改主界面UI");
                        break;
                    case DownloadThread.TYPE_FINISHED:
                        Log.e(TAG, "7.主线程知道Download线程下载完成了...这时候可以更改主界面UI,收工");
                        break;
                    default:
                        break;
                }
                super.handleMessage(msg);
            }
        };
        //子线程注入主线程的mUIhandler,可以在子线程执行任务的时候,随时发送消息回来主线程
        mHandlerThread.setUIHandler(mUIhandler);
        //子线程开始下载
        mHandlerThread.startDownload();
    }

    @Override
    protected void onDestroy() {
        //有2种退出方式
        mHandlerThread.quit();
        //mHandlerThread.quitSafely(); 需要API>=18
        super.onDestroy();
    }
} 

运行的Log日志如下

在这里插入图片描述 

源码解析

public class HandlerThread extends Thread {

 HandlerThread 本质是一个线程,只是其持有了 handler,所以可在子线程进行消息处理和分发。

接下去看下构造函数相关的:

    int mPriority;//优先级
    int mTid = -1;
    Looper mLooper;//自带的Looper
    private @Nullable Handler mHandler;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    
    /**
     * Constructs a HandlerThread.
     * @param name
     * @param priority The priority to run the thread at. The value supplied must be from 
     * {@link android.os.Process} and not from java.lang.Thread.
     */
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }

这里有两个构造方法,一个 
HandlerThread(String name),一个 
HandlerThread(String name, int priority),我们可以自己设定线程的名字以及优先级。注意!是 Process 里的优先级而不是Thread 的。
 /**
     * 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;
    }

这里面有一个方法 onLooperPrepared(),在实际中,我们可以重写这个方法做一些初始化的操作,这个 run() 是重点。

run 方法中首先获取线程 id,然后就调用了 Looper.prepare 方法创建一个 Looper,接着调用了 Looper.myLooper 方法获取到了当前线程的 Looper

接着通过 notifyAll 通知等带唤醒,这里的等待是在 HandlerThread 的 getLooper 方法里调用的 wait 方法,getLooper 方法是为了获取该 HandlerThread 中的 Looper

如果在没调用 HandlerThread 的 start 方法开启线程前就调用 getLooper 方法就通过 wait 方法暂时先进入等待,等到 run 方法运行后再进行唤醒。唤醒之后 run 方法中继续设置了构造函数中传入的优先级,接着调用了onLooperPrepared 方法,该方法是个空实现,该方法是为了在 Looper 开启轮询之前如果要进行某些设置,可以复写该方法。

最后调用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;
    }

 这个方法是获取当前的 Looper,可以看到如果没有获取的时候就一直等待直到获取,而前面也提到了获取到了就唤醒了所有的线程,看来这是线程的等待-唤醒机制应用。

public Handler getThreadHandler() {
        if (mHandler == null) {
            mHandler = new Handler(getLooper());
        }
        return mHandler;
    }

这个是获取 HandlerThread 绑定的 Looper 线程的 Handler 

public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit();
            return true;
        }
        return false;
    }
    
    
 public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quitSafely();
            return true;
        }
        return false;
    }

可以看到这两个方法去退出线程的 Looper 循环,那么这两个方法有什么区别呢,实际上都是调用了 MessageQueue 的 quit() 方法,源码如下:

void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
}

可以看到: 当我们调用 quit 方法的时候,实际上执行了 MessageQueue 中的 removeAllMessagesLocked 方法,该方法的作用是把 MessageQueue 消息池中所有的消息全部清空,无论是延迟消息(延迟消息是指通过 sendMessageDelayed 或通过 postDelayed 等方法发送的需要延迟执行的消息,只要不是立即执行的消息都是延迟消息)还是非延迟消息。

而 quitSafely 方法时,实际上执行了 MessageQueue 中的 removeAllFutureMessagesLocked 方法,通过名字就可以看出,该方法只会清空 MessageQueue 消息池中所有的延迟消息,并将消息池中所有的非延迟消息派发出去让 Handler 去处理,quitSafely 相比于 quit 方法安全之处在于清空消息之前会派发所有的非延迟消息,一句话,就是清除未来需要执行的消息。

这两个方法有一个共同的特点就是:Looper 不再接收新的消息了,消息循环就此结束,此时通过 Handler 发送的消息也不会在放入消息杜队列了,因为消息队列已经退出了。应用这2个方法的时候需要注意的是:quit 方法从 API 1 就开始存在了,比较早,而 quitSafely 直到 API 18 才添加进来.

总结

  • 如果经常要开启线程,接着又是销毁线程,这是很耗性能的,HandlerThread 很好的解决了这个问题;

  • HandlerThread 由于异步操作是放在 Handler 的消息队列中的,所以是串行的,但只适合并发量较少的耗时操作。

  • HandlerThread 用完记得调用退出方法。

  • 注意使用 handler 避免出现内存泄露

 
 
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/154240.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • java 除法取整_java 除法运算只保留整数位的4种方式

    java 除法取整_java 除法运算只保留整数位的4种方式1.情景展示根据提供的毫秒数进行除法运算,如果将毫秒数转换成小时,小时数不为0,则只取整数位,依此类推…2.情况分析可以使用3个函数实现Math.floor(num)  只保留整数位Math.rint(num)  余数四舍五入Math.ceil(num)  取整位,再+1举例:doublenum=3.1415926;System.out.println(Math.floor…

  • CTK插件框架学习3-第一个插件编写

    CTK插件框架学习3-第一个插件编写前两章把CTK插件库编译好了,这里篇编写一个插件试一下,共需要创建两个小工程,一个是插件库,一个是测试程序。1.插件库编写1.1创建工程打开Qtcreator,新建一个EmputyqmakeProject,并给工程命名为ctk-plugin-first。Kits选择”DesktopQt5.12.3MSVC201764bit”。更改ctk-plugin-first.pro文…

  • vue 部署上线清除浏览器缓存「建议收藏」

    vue 部署上线清除浏览器缓存「建议收藏」vue项目打包上线之后,每一次都会有浏览器缓存问题,需要手动的清除缓存。这样用户体验非常不好,所以我们在打包部署的时候需要尽量避免浏览器的缓存。下面是我的解决方案:一、修改根目录index.html在head里面添加下面代码<metahttp-equiv=”pragram”content=”no-cache”><metahttp-equiv=”cache-control”content=”no-cache,no-store,must-revalidate”>

  • pytest重试_pycharm could not find main

    pytest重试_pycharm could not find main安装:pip3installpytest-rerunfailures重新运行所有失败用例要重新运行所有测试失败的用例,请使用–reruns命令行选项,并指定要运行测试的最大次数:$py

  • 卷积神经网络(CNN)基础介绍

    卷积神经网络(CNN)基础介绍

  • CAN 接口测试[通俗易懂]

    CAN 接口测试[通俗易懂]CAN测试收发程序can发送测试#include<stdio.h>#include<stdlib.h>#include<string.h>#include<unistd.h>#include<net/if.h>#include<sys/ioctl.h>#include<sys/socket…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号