1. 程式人生 > >看完這篇。再也不怕被問 HandlerThread 的原理

看完這篇。再也不怕被問 HandlerThread 的原理

### 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,搭配執行耗時任務,適合序列執行耗時任務等