概述

HandlerThread 相信大家都比较熟悉了,从名字上看是一个带有 Handler 消息循环机制的一个线程,比一般的线程多了消息循环的机制,可以说是Handler + Thread 的结合,从源码上看也是如此的设计,一般情况下如果需要子线程和主线程之间相互交互,可以用 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日志如下

源码解析

先来看下构造函数相关的:

    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 避免出现内存泄露

 

Android HandlerThread 详解的更多相关文章

  1. Android HandlerThread详解

    概述 Android HandlerThread使用,自带Looper消息循环的快捷类. 详细 代码下载:http://www.demodashi.com/demo/10628.html 原文地址: ...

  2. android handle详解3 ThreadHandler

    在android handle详解2的基础上,我们来学习ThreadHandler ThreadHandler的本质就是对android handle详解2的实现 HandlerThread其实还是一 ...

  3. Android Notification 详解(一)——基本操作

    Android Notification 详解(一)--基本操作 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Notification 文中如有纰 ...

  4. Android Notification 详解——基本操作

    Android Notification 详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 前几天项目中有用到 Android 通知相关的内容,索性把 Android Notificatio ...

  5. Android ActionBar详解

    Android ActionBar详解 分类: Android2014-04-30 15:23 1094人阅读 评论(0) 收藏 举报 androidActionBar   目录(?)[+]   第4 ...

  6. Android 签名详解

    Android 签名详解 AndroidOPhoneAnt设计模式Eclipse  在Android 系统中,所有安装 到 系统的应用程序都必有一个数字证书,此数字证书用于标识应用程序的作者和在应用程 ...

  7. Android编译系统详解(一)

    ++++++++++++++++++++++++++++++++++++++++++ 本文系本站原创,欢迎转载! 转载请注明出处: http://blog.csdn.net/mr_raptor/art ...

  8. Android布局详解之一:FrameLayout

      原创文章,如有转载,请注明出处:http://blog.csdn.net/yihui823/article/details/6702273 FrameLayout是最简单的布局了.所有放在布局里的 ...

  9. 【整理修订】Android.mk详解

    Android.mk详解 1. Android.mk 的应用范围 Android.mk文件是GNU Makefile的一小部分,它用来对Android程序进行编译. 一个Android.mk文件可以编 ...

随机推荐

  1. HTML的 <u> 标签

    实例 使用 <u> 标签为文本添加下划线: <p>如果文本不是超链接,就不要<u>对其使用下划线</u>.</p> 亲自试一试 浏览器支持 ...

  2. Perl date time

    use Time::HiRes qw(time);use POSIX qw(strftime); my $t = time;my $date = strftime "%Y%m%d %H:%M ...

  3. django 简易博客开发 1 安装、创建、配置、admin使用(转)

    Django 自称是“最适合开发有限期的完美WEB框架”.本文参考<Django web开发指南>,快速搭建一个blog 出来,在中间涉及诸多知识点,这里不会详细说明,如果你是第一次接触D ...

  4. PowerPoint超链接字体颜色修改、怎么去掉超链接下划线

    经常在做PPT幻灯片时会遇到这样一个问题,给文字加超链接后发现链接的颜色是蓝色的,而且还带有下划线,这种效果与主题的色彩搭配简直是太影响美观效果了.有没有什么办法可以去掉PPT中的超链接下划线?再将超 ...

  5. Codeforces Avito Code Challenge 2018 D. Bookshelves

    Codeforces Avito Code Challenge 2018 D. Bookshelves 题目连接: http://codeforces.com/contest/981/problem/ ...

  6. 基于Thinkphp5.0 小程序登录插件应用

    资源连接: wulongtao/think-wxminihelper 具体怎么安装,不介绍了,有不懂再问我吧: 主要重点如下: wepy:index.wpy this.$parent.getUserI ...

  7. Apache Pulsar——企业级消息订阅系统介绍

    Apache Pulsar是一款由雅虎开发的类似于Kafka的企业级消息订阅系统,在2016将其开源,由Apach基金会孵化,现在已经成长为Apache基金会的顶级项目.Pulsar在雅虎内部已经运行 ...

  8. C#延时函数

    用Thread方法: 先using system.threading; 再在需要延时的进程处插入 thread.sleep(int);

  9. [BUG]自己的bug自己解,记一次在变量使用过程引发的bug

    [实现的功能要求]在短信编辑界面,将所有的emoji表情全部插入到编辑区域,其中表情共有5页,每遍历完一页时需要自动翻页重新获取表情并插入,在第5页中只有10个表情 下面先看看这段代码,大家能否看出有 ...

  10. @RequestParam注解的作用及用法

    最简单的两种写法,在写接口时:加或不加@RequestParam注解的区别 第一种写法参数为非必传,第二种写法参数为必传.参数名为userId 第二种写法可以通过@RequestParam(requi ...