看完這篇。再也不怕被問 HandlerThread 的原理
阿新 • • 發佈:2020-07-28
### HandlerThread是什麼
![image-20200728000754030](http://picbed-dmingou.oss-cn-shenzhen.aliyuncs.com/img/image-20200728000754030.png)
官網介紹
```
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。
```java
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`的背後原理:
```java
handlerThread = new HandlerThread("HandlerThread-1");
handlerThread.start();
taskHandler = new TaskHandler(handlerThread.getLooper());
```
看下`getLooper()`葫蘆裡什麼藥:
```java
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()`方法有什麼名堂:
```java
@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目前被**隱藏**了:
```java
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的辦法,看以下兩個方法:
```java
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,搭配執行耗時任務,適合序列執行耗時任務等