詳解HandlerThread

不達成功誓不休 — 程端禮

寫在前面

“送走四月,喜迎五月”,今年的五一小長假簡直舒服到了極致,犧牲兩個周六換來的四天假期。是時候出去浪了, 打開微博發現不妙,嚇得我趕緊躺床上又睡了一覺...

入職后經常加班,僅有的私人時間也沒有心思學習。不過我還是告訴自己在忙也不能忘了充實自己。趁著今天有興致,閱讀了一下HandlerThread的源碼。

用法

首先,我們來了解一下HandlerThread。

Q:什么是HandlerThread?
A:HandlerThread繼承自Thread,可以說其是一個線程。并且有一個Looper對象進行消息循環。

接下來,我們看一下HandlerThread是如何執行異步任務的。

// 創建HandlerThread對象,參數為該線程的Name
HandlerThread handlerThread = new HandlerThread("work");
// 調用HandlerThread的start()函數開啟線程
handlerThread.start();

// 通過調用HandlerThread的getLooper()函數得到Looper對象,并創建Handler用于執行異步任務
Handler workHandler = new Handler(handlerThread.getLooper(), new WorkHandlerCallback());

// 在主線程中創建Handler,用于更新UI(主線程中有一個Looper)
Handler uiHandler = new Handler(new UIHandlerCallback());

// 發送一個異步任務的消息
Message msg = workHandler.obtainMessage();
msg.what = 0x01;
workHandler.sendMessage(msg);

private class WorkHandlerCallback implements Handler.Callback {

       @Override
       public boolean handleMessage(Message msg) {
         switch (msg.what) {
                case 0x01:
                    // 開始執行異步任務
                    int sum = 0;
                    for (int i = 0 ; i < 100 ; i ++) {
                        sum += i;
                    }
                    // 異步任務執行成功后,告知UI線程更新
                    Message message = mUiHandler.obtainMessage();
                    message.what = 0x02;
                    message.obj = sum + "";
                    mUiHandler.sendMessage(message);
                    break;
                    default:
                        break;
            }
           return true;
       }
}

private class UIHandlerCallback implements Handler.Callback {

       @Override
       public boolean handleMessage(Message msg) {
         switch (msg.what) {
                case 0x02:
                    // 更新UI
                    textView.setText((String) msg.obj);
                    // 異步任務做完以后,不再需要HandlerThread,就調用quit()函數退出
                    handlerThread.quit();
                    break;
                    default:
                        break;
            }           return false;
       }
}

以上就是HandlerThread的基本用法,是不是很簡單呢,通過簡單的幾步就可以在異步線程中做耗時任務,再也不用擔心主線程阻塞了。

注意:

  • HandlerThread也是一個線程,想要其工作就一定要調用start()函數,而且Looper對象是在run()函數中創建的,只有run()函數執行了,才會創建Looper對象進行消息循環。
  • 通過HandlerThread的getLooper()創建的Handler只能用來執行異步任務,因為它不在主線程中,無法進行UI更新,適合做一些耗時任務。

源碼

學會了使用HandlerThread,那么就不想知道它的內部是如何實現的嗎?反正我是挺想的,也不知道你想不想,咱也不知道,咱也不敢問,有興趣的童鞋就和我一起探其究竟吧。

public class HandlerThread extends Thread {
    // 線程優先級
    int mPriority;
    // 線程id
    int mTid = -1;
    // 該線程所持有的Looper對象
    Looper mLooper;
    // 線程內部的Handler,可以通過getThreadHandler()函數直接獲取使用
    private @Nullable Handler mHandler;
    
    // 擁有一個參數的構造函數
    // 傳入參數為線程名稱,具有默認線程優先級
    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    
    // 擁有兩個參數的構造函數
    // 傳入的參數name是線程名稱,參數priority是線程優先級(線程優先級詳見Process類)
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
    
    // 該函數為空實現,子類可以重寫該函數,在Looper進行消息循環之前調用
    protected void onLooperPrepared() {
    }

    // 調用該線程的start()函數后,run()函數會被執行
    // run()函數可以說是HandlerThread的核心,該函數內部會創建Looper進行消息循環
    @Override
    public void run() {
        // 獲取該線程的id
        mTid = Process.myTid();
        // 為該線程創建Looper
        Looper.prepare();
        // 通過持有同步鎖機制得到該線程的Looper對象
        // 然后調用notifyAll()函數通知getLooper()函數Looper對象已經創建完成。
        synchronized (this) {
            // 獲取該線程的Looper對象
            mLooper = Looper.myLooper();
            // 喚醒在當前對象監視器上等待的所有線程
            notifyAll();
        }
        // 設置線程優先級
        Process.setThreadPriority(mPriority);
        // 調用onLooperPrepared()函數,子類可以在消息循環之前做一些準備工作
        onLooperPrepared();
        // 開始消息循環
        Looper.loop();
        mTid = -1;
    }
    
    // 獲取該線程的Looper對象
    public Looper getLooper() {
        // 如果該線程不是isAlive,則直接返回null
        if (!isAlive()) {
            return null;
        }
        
        // 通過持有同步鎖機制判斷當前是否創建了Looper對象
        // 如果該線程沒有start,即還沒有創建Looper對象
        // 則調用wait()函數,使當前對象上的線程進入等待,就是等待run()函數中的notifyAll()函數執行
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

    // 可以直接獲取一個Handler對象
    @NonNull
    public Handler getThreadHandler() {
        if (mHandler == null) {
            // 創建Handler實例,參數為該線程的Looper
            mHandler = new Handler(getLooper());
        }
        return mHandler;
    }

    // 退出消息循環,效率高,但不是線程安全的
    public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            // 調用Looper對象的quit()函數退出消息循環,
            // 內部調用的是MessageQueue的quit(boolean safe)函數,傳入參數為false
            looper.quit();
            return true;
        }
        return false;
    }

    // 退出消息循環,效率低,但是線程安全的
    public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            // 調用Looper對象的quitSafely()函數退出消息循環
            // 內部調用的是MessageQueue的quit(boolean safe)函數,傳入參數為true
            looper.quitSafely();
            return true;
        }
        return false;
    }

    // 獲取當前線程id
    public int getThreadId() {
        return mTid;
    }
}

總結

分析了HandlerThread的源碼后,總結為以下幾點:

  • HandlerThread有兩個構造函數,分別是線程名稱或線程名稱和線程優先級。
  • HandlerThread的本質就是一個線程,但不同于線程,它的核心就是run()函數,它在run()函數內部創建一個Looper對象進行消息循環,使通過該Looper創建的Handler實例運行在該線程中。為了避免getLooper()獲取到的Looper對象為空,采用了同步鎖機制,即在getLooper()函數中若mLooper為null,則讓當前對象上的線程進入等待,直到run()函數中創建好Looper對象后,喚醒在當前對象監視器上等待的所有線程,即getLooper()函數繼續執行。
  • HandlerThread內部提供了一個Handler,其創建方式也是通過getLooper()函數獲取該線程的Looper對象進行創建,不過該Handler只有在外部調用getThreadHandler()時才會進行創建。
  • HandlerThread有兩種退出方式,一種不是線程安全的,但是效率高;一種是線程安全的,但是效率低;其最終調用的都是MessageQueue的quit(boolean safe)函數。
  • HandlerThread和Thread一樣,都是調用start()函數開啟線程。
  • HandlerThread的構造函數需要傳入線程名稱,而Thread的構造函數則不需要。
  • 繼承自Thread的子類需要重寫其run()函數,在其中執行任務;而HandlerThread的子類可以重寫 onLooperPrepared()函數,在創建Looper對象之前做準備工作。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容