Android并发编程之HandlerThread学习
[TOC]
概述
初次看到HandlerThread的名字,我们可能会联想到Handler和Thread这两个类,没错,它其实就是跟Handler和Thread有莫大的关系。HandlerThread继承自Thread,所以它本质上就是一个Thread,那既然是Thread,那么必然就应该是用来处理耗时任务,而且专门用来处理Handler中的耗时任务。
HandlerThread简介
看看官方对它的解释:
++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可以创建一个带有looper的线程,looper对象可以用于创建Handler类来进行来进行调度,而且start()方法必须被调用。
在Android开发中,不熟悉多线程开发的人一想到要使用线程,可能就用
1 | new Thread(){…}.start(); |
这样的方式。实质上在只有单个耗时任务时用这种方式是可以的,但若是有多个耗时任务要串行执行呢?那不得要多次创建多次销毁线程,这样导致的代价是很耗系统资源,容易存在性能问题。那么,怎么解决呢?
我们可以只创建一个工作线程,然后在里面循环处理耗时任务,创建过程如下:
1 | Handler mHandler; |
在该工作线程中,调用Looper.prepare()创建与当前线程绑定的Looper实例;
使用上面创建的Looper生成Handler实例;
调用Looper.loop()实现消息循环;
然后透过Looper的循环,在Handler的handlerMessage()中进行异步任务的循环处理。而这也正好是HandlerThread的实现。
关于源码,我们后面再进行解析。
我们先来看一下HandlerThread的使用
HandlerThread使用步骤
创建HandlerThread实例对象
1 | /** |
HandlerThread默认有两个构造函数,提供了线程名参数和线程优先级参数的设置。
启动HandlerThread线程
1 | handlerThread.start(); |
通过start()方法就可以启动一个HandlerThread了,该线程会不断地循环运行。
通过Handler构建循环消息处理机制。
1 | Handler workderHandler = new Handler(handlerThread.getLooper(), new Handler.Callback() { |
通过将HandlerThread绑定的Looper对象传递给Handler作为参数,构建一个异步的Handler对象,为了能实现耗时任务的异步执行,我们重写了Handler的Callback接口的handleMessage()方法,当然也可以不重写该方法,而通过post()方法进行耗时任务操作。
1 | Handler workderHandler = new Handler(handlerThread.getLooper()); |
最后,我们就可以通过调用workerHandler以发送消息的形式发送耗时任务到工作线程HandlerThread中去执行,实际上就是在Handler.Callback里的handleMessage()中执行。
这里要注意,在创建Handler作为HandlerThread线程消息执行者的时候必须先调用start()方法,因为创建Handler所需要的Looper参数是从HandlerThread中获得的,而Looper对象的赋值又是在HandlerThread的run()方法中创建。
HandlerThread源码解析
我们来看下它的构造函数:
1 | /** |
从代码中得知,HandlerThread带有两个构造函数,可传递两个参数,一个参数是name,指的是线程的名称,
另一个参数是priority,指的是线程优先级。
线程的优先级的取值范围为-20到19。优先级高的获得的CPU资源更多,反之则越少。-20代表优先级最高,19最低。
我们可以根据自己的需要去设置线程的优先级,也可以采用默认的优先级,HandlerThread的默认优先级是Process.THREAD_PRIORITY_DEFAULT,具体值为0。该优先级是再run()方法中设置的,我们看它的run()方法:
1 | public class HandlerThread extends Thread { |
run()方法主要是通过Looper.prepare()和Looper.loop()构造了一个循环线程。这里要注意,在创建HandlerThread对象后必须调用其start()方法才能进行run()方法体的执行。
在Looper.prepare()执行后,Looper对象会被创建,然后通过同步锁机制,将Looper对象赋值给HandlerThread的内部变量mLooper,并通过notifyAll()方法去唤醒等待线程。接着为线程赋予优先级,然后执行onLooperPrepared()方法,该方法是一个空实现,留给我们必要的时候去重写的,主要用来做一些初始化工作。最后通过执行Looper.loop()在线程中启动消息队列。
我们看到在run()方法中进行了唤醒等待线程,为什么要这么做呢?答案就在getLooper()方法中:
HandlerThread的使用场景
HandlerThread是自带消息循环的线程,比较适合那种长时间在后台执行的消息任务队列。比如,我们在进行跨进程通信的时候维持消息传输的生产者、消费者队列。因为长时间在后台运行,他才能不断地从消息队列中取出消息来进行消息的分发,耗时处理。
HandlerThread比较适用于单线程+异步队列的场景,比如IO读写操作数据库、文件等,耗时不多而且也不会产生较大的阻塞。
对于网络IO操作,HandlerThread并不适合,因为它只有一个线程,还得排队一个一个等着。
这里单线程+异步队列模型的使用场景还举几个例子:
- 1.应用刚启动时的数据初始化操作,如果开启多个线程同时执行,有可能争夺UI线程的CPU执行时间,造成卡顿,而使用该模型,通过设置优先级就可以将同步工作顺序的执行,而又不影响UI的初始化;
- 2.从数据库中读取数据展现在ListView中,通过Handler的postAtFrontOfQueue方法,快速将读取操作加入队列前端执行,必要时返回给主线程更新UI;
- 3.HandlerThread应用在IntentService中,IntentService是一个需要长时间执行任务Service,优先级要比线程高。
- 4.我们在进行跨进程通信的时候维持消息传输的生产者、消费者队列。因为长时间在后台运行,他才能不断地从消息队列中取出消息来进行消息的分发,耗时处理。