HandlerThread是什么

官网介绍

A Thread that has a Looper. The Looper can then be used to create Handlers.
Note that just like with a regular Thread, Thread.start() must still be called.

翻译:

HandlerThread,持有一个可用来构建Handlers的Looper,像一个常规的线程类,必须要调用start()才能正常工作。

HandlerThread的父类是Thread,所以HandlerThread的本质还是一个线程,但是它并非像Thread需要在run代码块内执行耗时的任务,HandlerThread是通过搭配外部的Handler分发处理消息执行任务的,可以很简单地返回和管理子线程的一个Looper对象。

HandlerThread常见的使用场景

有两个耗时任务A、B,任务B的执行需要A执行结果,即 A,B不可以并行执行,而是要串行按顺序执行任务。

下面给出模拟这种场景HandlerThread使用的实例代码:(代码可直接复制运行,有点长有点渣,见谅)

getResultA()doThingB(),模拟了A,B两个不可以并行执行的耗时任务。

taskHandlerHandler子类的实例,通过获取handlerThread开启后创建的Looper,串行发送了消息A,消息B,Looper自然也是先取出消息A,给taskHandler.handleMessage处理,再取出消息B完成了串行执行耗时任务A、B。

完成了串行执行耗时任务A、B。

public class HandlerThreadActivity extends AppCompatActivity {

    private Handler taskHandler;
private HandlerThread handlerThread; private static String resultA; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
handlerThread = new HandlerThread("HandlerThread-1");
//!!关键:HandlerThread需要调用start开启线程,否则持有Looper为null
handlerThread.start();
//使用handlerThread线程持有的Looper构建 taskHandler实例
taskHandler = new TaskHandler(handlerThread.getLooper());
//发送消息A
Message msgA = Message.obtain();
msgA.what = 0;
msgA.obj = "Task-A";
taskHandler.sendMessage(msgA);
//发送消息B
Message msgB = Message.obtain();
msgB.what = 1;
msgB.obj = "Task-B";
taskHandler.sendMessage(msgB);
} @Override
protected void onDestroy() {
super.onDestroy();
//手动退出HandlerThread的Looper
handlerThread.quitSafely();
} @WorkerThread
private static String getResultA() {
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "DMingO";
} @WorkerThread
private static void doThingB() {
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " :"+resultA + " 's blog");
} private static class TaskHandler extends Handler{ public TaskHandler(@NonNull Looper looper) {
super(looper);
} @Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 0:
//执行耗时任务 getResultA()
resultA = getResultA();
break;
case 1:
if(! "".equals(resultA)){
//拿到任务A的返回结果才能执行任务B
doThingB();
}
break;
default:
break;
}
}
}
}

运行结果:

可以看到TaskHandler.handleMessage是运行在HandlerThread这一个线程上,归根结底还是HandlerThread把它线程的Looper给了TaskHandler实例

I/System.out: HandlerThread-1 :DMingO 's blog

HandlerThread起的最大作用就是 很简便地提供了一个可设置命名和优先级的线程的Looper对象

HandlerThread源码分析

通过最简单的使用入手分析HandlerThread作为一个线程,提供一个子线程的Looper的背后原理:

        handlerThread = new HandlerThread("HandlerThread-1");
handlerThread.start();
taskHandler = new TaskHandler(handlerThread.getLooper());

看下getLooper()葫芦里什么药:

    public Looper getLooper() {
//isAlive()判断当前线程是否已经开启
//如果线程未开启(未调用HandlerThread.start),会返回null
//所以必须执行了start()后,才能调用 getLooper(),否则会有空指针异常
if (!isAlive()) {
return null;
} // 如果线程已开启但Looper未被创建,会进入同步代码块,阻塞-->直到Looper被创建
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
//mLooper==null-->线程进入阻塞状态
wait();
} catch (InterruptedException e) {
}
}
}
//确保 返回的mLooper不为null
return mLooper;
}

通过分析,getLooper() 方法确保可以返回一个HandlerThread线程持有的且非空的Looper对象。前提是HandlerThread线程已经开启。如果线程已开启但Looper未被创建,线程会阻塞,直到Looper被创建了。

那么在哪个方法,mLooper才被赋值,Looper对象才被创建呢?还记得 getLooper() 方法在最初如果发现线程未被开启,直接就返回null,这不就说明HandlerThread线程的开启与否与它的Looper创建,这两者息息相关嘛。

那就再看下HandlerThread的run()方法有什么名堂:

    @Override
public void run() {
mTid = Process.myTid();
//创建此线程的Looper和MessageQueue
Looper.prepare();
synchronized (this) {
//给 mLooper 赋值
mLooper = Looper.myLooper();
//此时mLooper!=null-->取消线程阻塞
notifyAll();
}
//为线程设置mPriority优先级
Process.setThreadPriority(mPriority);
onLooperPrepared();
//开始运行 Looper
Looper.loop();
mTid = -1;
}

开启HandlerThread线程后,会创建此线程的Looper和MessageQueue,设置线程优先级,开始Looper的循环取消息。

欸,HandlerThread这名字,它的Handler又去哪儿了呢?emmmm目前被隐藏了:

    private @Nullable Handler mHandler;

    /**
* 返回与此线程相关联的一个Handler实例
* @hide 目前此方法是被隐藏的,无法正常直接调用
*/
@NonNull
public Handler getThreadHandler() {
if (mHandler == null) {
mHandler = new Handler(getLooper());
}
return mHandler;
}

可以看出,HandlerThreadmHandler的实例化是属于懒加载方式,只能在外界调用 getThreadHandler()的时候,才会对mHandler判空&进行实例化。实例化时传入的Looper对象自然是HandlerThread这一线程创建的Looper。因此若Looper还未被初始化,方法也会一直阻塞直到Looper创建完成,也需要线程已开启。

毫无疑问,mHandler 也自然也是只能去处理HandlerThread这一个线程的消息。

可以看出HandlerThread这个类与Looper的关系是密不可分的,自然也会有退出Looper的办法,看以下两个方法:

    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自然也为null),返回 false
  • 如果线程已经开启了,则会调用 Looper类的quit() / quitSafely()方法,并返回 true

不同的是,根据官方描述,建议使用quitSafely(),这会允许消息队列中还在排队的消息都被取出后再关闭,避免所有挂起的任务无法有序的被完成。

HandlerThread分析总结

HandlerThread 本质是一个Thread,却和普通的 Thread很不同的是:普通的 Thread 主要被用在 run 方法中执行耗时任务,而 HandlerThread 在线程开启后(run方法中)创建了该线程的Looper和消息队列,外界Handler可以很方便获取到这个Looper,搭配执行耗时任务,适合串行执行耗时任务等场景。

看完这篇。再也不怕被问 HandlerThread 的原理的更多相关文章

  1. 看完这篇还不会 GestureDetector 手势检测,我跪搓衣板!

    引言 在 android 开发过程中,我们经常需要对一些手势,如:单击.双击.长按.滑动.缩放等,进行监测.这时也就引出了手势监测的概念,所谓的手势监测,说白了就是对于 GestureDetector ...

  2. 看完这篇Redis缓存三大问题,保你面试能造火箭,工作能拧螺丝。

    前言 日常的开发中,无不都是使用数据库来进行数据的存储,由于一般的系统任务中通常不会存在高并发的情况,所以这样看起来并没有什么问题. 一旦涉及大数据量的需求,如一些商品抢购的情景,或者主页访问量瞬间较 ...

  3. APP的缓存文件到底应该存在哪?看完这篇文章你应该就自己清楚了

    APP的缓存文件到底应该存在哪?看完这篇文章你应该就自己清楚了 彻底理解android中的内部存储与外部存储 存储在内部还是外部 所有的Android设备均有两个文件存储区域:"intern ...

  4. 关于 Docker 镜像的操作,看完这篇就够啦 !(下)

    紧接着上篇<关于 Docker 镜像的操作,看完这篇就够啦 !(上)>,奉上下篇 !!! 镜像作为 Docker 三大核心概念中最重要的一个关键词,它有很多操作,是您想学习容器技术不得不掌 ...

  5. 【最短路径Floyd算法详解推导过程】看完这篇,你还能不懂Floyd算法?还不会?

    简介 Floyd-Warshall算法(Floyd-Warshall algorithm),是一种利用动态规划的思想寻找给定的加权图中多源点之间最短路径的算法,与Dijkstra算法类似.该算法名称以 ...

  6. [转帖]看完这篇文章你还敢说你懂JVM吗?

    看完这篇文章你还敢说你懂JVM吗? 在一些物理内存为8g的服务器上,主要运行一个Java服务,系统内存分配如下:Java服务的JVM堆大小设置为6g,一个监控进程占用大约 600m,Linux自身使用 ...

  7. [转帖]看完这篇文章,我奶奶都懂了https的原理

    看完这篇文章,我奶奶都懂了https的原理 http://www.17coding.info/article/22 非对称算法 以及 CA证书 公钥 核心是 大的质数不一分解 还有 就是 椭圆曲线算法 ...

  8. Mysql快速入门(看完这篇能够满足80%的日常开发)

    这是一篇mysql的学习笔记,整理结合了网上搜索的教程以及自己看的视频教程,看完这篇能够满足80%的日常开发了. 菜鸟教程:https://www.runoob.com/mysql/mysql-tut ...

  9. 看完这篇 Linux 权限后,通透了!

    我们在使用 Linux 的过程中,或多或少都会遇到一些关于使用者和群组的问题,比如最常见的你想要在某个路径下执行某个指令,会经常出现这个错误提示 . permission denied 反正我大概率见 ...

随机推荐

  1. jmeter跨线程组获取cookie或jmeter线程组共享cookie-笔者亲测

    一.Jmeter版本 此次示例采用的是apache-jmeter-5.2.1版本 二.设置配置文件使Cookie管理器保存cookie信息. 修改apache-jmeter-5.2.1/bin/jme ...

  2. 洛谷 P1215 【[USACO1.4]母亲的牛奶 Mother's Milk】

    这道题\(DFS\)就好了,六种情况,\(ab,ac,ba,bc,ca,cb\),我们直接枚举就可.什么?这样不会结束?用一个\(vis\)数组判断走过没有就可以了.最后排序输出即可. \(code: ...

  3. 阿里云centos7安装jdk8

    1.准备Linux版本的jdk8直接上Oracle公司的官网下载就好了    http://www.oracle.com/technetwork/java/javase/downloads/jdk8- ...

  4. 想做时间管理大师?你可以试试Mybatis Plus代码生成器

    1. 前言 对于写Crud的老司机来说时间非常宝贵,一些样板代码写不但费时费力,而且枯燥无味.经常有小伙伴问我,胖哥你怎么天天那么有时间去搞新东西,透露一下秘诀呗. 好吧,今天就把Mybatis-pl ...

  5. 第一章 java基本多线程技能

    第一章 java多线程技能 1 线程:进程是操作系统结构的基础,是一次程序的执行,是一个程序及其数据在处理顺序时发生的活动:是程序在一个数据集合上运行的过程,他是系统进行资源分配和调度的一个独立单位. ...

  6. UVA - 11300 Spreading the Wealth(数学题)

    UVA - 11300 Spreading the Wealth [题目描述] 圆桌旁边坐着n个人,每个人有一定数量的金币,金币的总数能被n整除.每个人可以给他左右相邻的人一些金币,最终使得每个人的金 ...

  7. java 和 c++ 的三目运算符的区别

    转载请注明出处:http://www.cnblogs.com/liangyongrui/p/6348001.html 以前很少用java,就知道java和c++差不多. 今天就踩了一个坑. 不吐糟,直 ...

  8. 13.Camera摄像机常用属性

    选中Main Camera相机,在最右侧的Inspector面板可以看到有Camera组件,该组件下有一些摄像机常用的属性. 1.Clear Flags属性 SkyBox:天空盒(默认效果,场景会有天 ...

  9. Mysql 实例:mysql语句练习50题(普通sql写法)

    为了练习sql语句,在网上找了一些题,自己做了一遍,收益颇多.很多地方换一种思路,有更好的写法,欢迎指正. 题目地址:https://blog.csdn.net/fashion2014/article ...

  10. Flask 基础组件(一):基本使用

    Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架,对于Werkzeug本质是Socket服务端,其用于接收http请求并对请求进行预处理,然后 ...