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

DMingO發表於2020-07-28

HandlerThread是什麼

image-20200728000754030

官網介紹

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兩個不可以並行執行的耗時任務。

taskHandlerHandler子類的例項,通過獲取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;
    }

可以看出,HandlerThreadmHandler的例項化是屬於懶載入方式,只能在外界呼叫 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,搭配執行耗時任務,適合序列執行耗時任務等場景。

相關文章