持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第12天,点击查看活动详情
HandlerThread是什么
首先,我们来看看,假如我们需要在handleMessage
方法中,启用线程处理数据该怎么做:
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
new Thread(new Runnable() {
@Override
public void run() {
Log.i("处理数据的线程", Thread.currentThread().getName());
}
}).start();
}
};
Log.i("Handler所在的线程:", Thread.currentThread().getName());
handler.sendEmptyMessage(1);
复制代码
结果输出:
I/Handler所在的线程:: main
I/处理数据的线程: Thread-17
复制代码
但是,假如对于 Handler 机制比较了解的话,就会清楚,其实handleMessage
方法的执行线程是Looper
所在的线程,也就是说,在创建Handler
的时候,假如传入其它线程的Looper
,这时,handleMessage
方法就在其它线程运行了。
class TestThread extends Thread {
private Looper looper;
@Override
public void run() {
Looper.prepare();
synchronized (this){
looper = Looper.myLooper();
notifyAll();
}
Looper.loop();
}
public Looper getLooper() {
if (looper == null) {
synchronized (this){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
return looper;
}
}
复制代码
TestThread testThread = new TestThread();
testThread.start();
Handler handler = new Handler(testThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
Log.i("处理数据的线程", Thread.currentThread().getName());
}
};
Log.i("Handler所在的线程:", Thread.currentThread().getName());
handler.sendEmptyMessage(1);
复制代码
结果输出:
I/Handler所在的线程:: main
I/处理数据的线程: Thread-15
复制代码
TestThread分析
为什么TestThread
写得这么复杂?
1、不能直接返回Looper.myLooper()
Looper.myLooper()
返回的是当前线程中的Looper
。若直接使用TestThread
对象直接调用getLooper()
返回Looper.myLooper()
,那么返回的Looper
为调用TestThread
对象的线程的Looper
,在本文例子中,则为返回主线程的Looper
。
所以,需要在run()
方法中初始化Looper
,并将其值保存起来。
2、为什么要使用wait()
和notifyAll()
因为Looper
是在run()
中进行初始化的,也就是说开启线程进行初始化,是异步的,在调用完TestThread
的start()
方法后,直接获取Looper
有可能为null,所以需要使用
wait()
和notifyAll()
确保一定能够获取得到Looper
对象。
嗯?是不是走远了?怎么这么久都还没讲到HandlerThread
是什么。
其实,HandlerThread
其实就是TestThread
,只不过TestThread
是简化版,但是核心逻辑基本一致。其继承于线程,并且对于新线程的Looper
进行初始化并管理,所以,应该叫做LooperThread
或许更加合适。
HandlerThread的简单使用
//初始化HandlerThread,传入的值为线程名字
HandlerThread handlerThread = new HandlerThread("ThreadName");
//记得一定要调用start(),其启动线程初始化Looper
handlerThread.start();
//初始化Handler并传入其它线程的Looper
Handler handler = new Handler(handlerThread.getLooper()){
@Override
public void handleMessage(Message msg) {
Log.i("处理数据的线程", Thread.currentThread().getName());
}
};
Log.i("Handler所在的线程:", Thread.currentThread().getName());
//发送消息
handler.sendEmptyMessage(1);
//不使用后,要对于Looper进行退出操作
handlerThread.quit();
复制代码
代码的注释已经写得够明白了,下面我们来看看输出结果:
I/Handler所在的线程:: main
I/处理数据的线程: ThreadName
复制代码
HandlerThread源码分析(源码只保留关键部分,并非全部源码)
同样的,我们还是从简单使用的步骤,一步步分析:
HandlerThread初始化
HandlerThread handlerThread = new HandlerThread("ThreadName");
复制代码
其源码:
public HandlerThread(String name) {
super(name);
···
}
复制代码
super(name)
是调用父类Thread
的构造方法:
public Thread(String name) {
init(null, null, name, 0);
}
复制代码
这个我们就不深究了,这属于Thread
的方法,知道这是为了初始化Thread
即可。
HandlerThread start() 的调用
handlerThread.start();
复制代码
因为handlerThread
本质也是一个Thread
,所以,我们重点看看handlerThread
对于run()
的重写:
@Override
public void run() {
···
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
···
onLooperPrepared();
Looper.loop();
···
}
protected void onLooperPrepared() {
}
复制代码
其实就是对于Looper
进行初始化,并且把初始化后的Looper
对象存储到mLooper
中,并且调用onLooperPrepared()
。
我们可以看到,onLooperPrepared()
里面没有任何代码,用于用户重写该方法,执行些Looper
初始化后的一些操作。
HandlerThread的Looper获取
handlerThread.getLooper()
复制代码
public Looper getLooper() {
···
// 避免获取Looper的时候,Looper还未初始化(因为Looper是启动线程进行初始化)
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
复制代码
HandlerThread的quit()
handlerThread.quit();
复制代码
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
复制代码
public void quit() {
//mQueue为MessageQueue
mQueue.quit(false);
}
复制代码
结束消息队列,进而结束线程。
所以,使用完HandlerThread
一定要记得调用quit()
,否则线程将会一直存在!