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兩個不可以並行執行的耗時任務。
taskHandler
是Handler子類的實例,通過獲取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;
}
可以看出,HandlerThread
的mHandler的實例化是屬於懶加載方式,只能在外界調用 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,搭配執行耗時任務,適合串行執行耗時任務等場景。