HandlerThread原始碼解析

iceCola7發表於2018-04-27

一、HandlerThread簡介

在Android系統中,我們執行完耗時操作都要另外開啟子執行緒來執行,執行完執行緒以後執行緒會自動銷燬。想象一下如果我們在專案中經常要執行耗時操作,如果經常要開啟執行緒,接著又銷燬執行緒,這無疑是很消耗效能的?那有什麼解決方法呢?

  • 使用執行緒池
  • 使用HandlerThread

Handy class for starting a new thread that has a looper. The looper can then be used to create handler classes. Note that start() must still be called.

一個好用的類用於建立一個自帶Looper的執行緒。這個Looper可以用來建立Handler。注意start()方法必須首先被呼叫。

1.HandlerThread使用場景

HandlerThreadGoogle幫我們封裝好的,可以用來執行多個耗時操作,而不需要多次開啟執行緒,裡面是採用HandlerLooper實現的。

2.HandlerThread簡單使用

//建立mHandlerThread
mHandlerThread = new HandlerThread("main");
//獲取HandlerThead中的Looper
Looper looper = mHandlerThread.getLooper();
//建立子執行緒中的Looper
Handler handler = new Handler(looper);
//執行耗時操作
handler.post(new Runnable() {
    @Override
    public void run() {
        //子執行緒中執行耗時操作
    }
});

//介面銷燬的時候需要銷燬Looper
@Override
protected void onDestroy() {
    super.onDestroy();
    mHandlerThread.quit();
}
複製程式碼

二、HandlerThread原始碼分析

1.成員變數

int mPriority; // 執行緒優先順序
int mTid = -1; // 執行緒ID
Looper mLooper; // 建立執行緒的Looper
複製程式碼

2.構造方法

public HandlerThread(String name) {
    super(name);
    mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
public HandlerThread(String name, int priority) {
    super(name);
    mPriority = priority; // 執行緒的優先順序
}
複製程式碼

有兩個構造方法,一個方法和兩個方法,name是執行緒的名稱,priority是執行緒的優先順序。

3.啟動執行緒要呼叫start()方法,實際上就是執行了run()方法

public void run() {
    mTid = Process.myTid();
    Looper.prepare();
    //持有鎖機制來獲得當前執行緒的Looper物件
    synchronized (this) {
        mLooper = Looper.myLooper();
        //發出通知,當前執行緒已經建立mLooper物件成功,這裡主要是通知getLooper方法中的wait
        notifyAll();
    }
    //設定執行緒的優先順序別
    Process.setThreadPriority(mPriority);
    //這裡預設是空方法的實現,我們可以重寫這個方法來做一些執行緒開始之前的準備,方便擴充套件
    onLooperPrepared();
    Looper.loop();
    mTid = -1;
}
複製程式碼

4.那麼為什麼會有鎖機制以及notifyAll()方法,原因就在getLooper()方法

public Looper getLooper() {
    if (!isAlive()) {
        return null;
    }
    
    // If the thread has been started, wait until the looper has been created.
    // 如果執行緒已經啟動,請等到建立了Looper。
    synchronized (this) {
        while (isAlive() && mLooper == null) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }
    }
    return mLooper;
}
複製程式碼

在獲取mLooper物件的時候存在一個同步問題,只有等到執行緒啟動以及Looper物件成功建立之後才能獲得mLooper的值。

5.下面看下quit()quitSafe()兩個方法

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.quit()looper.quitSafely(),分別跟蹤這兩個方法

public void quit() {
    mQueue.quit(false);
}
public void quitSafely() {
    mQueue.quit(true);
}
複製程式碼

mQueue.quit()方法一個傳入了false一個傳入了true

void quit(boolean safe) {
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }

    synchronized (this) {
        if (mQuitting) {
            return;
        }
        mQuitting = true;

        if (safe) {
            // 安全退出呼叫這個方法
            removeAllFutureMessagesLocked();
        } else {
            // 不安全退出執行這個方法
            removeAllMessagesLocked();
        }
        // We can assume mPtr != 0 because mQuitting was previously false.
        nativeWake(mPtr);
    }
}
複製程式碼

不安全的會呼叫removeAllMessagesLocked()這個方法,我們來看這個方法是怎樣處理的,其實就是遍歷Message連結串列,移除所有資訊的回撥,並重置為null

private void removeAllMessagesLocked() {
    Message p = mMessages;
    while (p != null) {
        Message n = p.next;
        p.recycleUnchecked();
        p = n;
    }
    mMessages = null;
}
複製程式碼

安全地會呼叫removeAllFutureMessagesLocked()這個方法,它會根據Message.when這個屬性,判斷我們當前訊息佇列是否正在處理訊息,沒有正在處理訊息的話,直接移除所有回撥,正在處理的話,等待該訊息處理處理完畢再退出該迴圈。因此說quitSafe()是安全的,而quit()方法是不安全的,因為quit()方法不管是否正在處理訊息,直接移除所有回撥。

private void removeAllFutureMessagesLocked() {
    final long now = SystemClock.uptimeMillis();
    Message p = mMessages;
    if (p != null) {
        //判斷當前佇列中的訊息是否正在處理這個訊息,沒有的話,直接移除所有回撥
        if (p.when > now) {
            removeAllMessagesLocked();
        } else {//正在處理的話,等待該訊息處理處理完畢再退出該迴圈
            Message n;
            for (;;) {
                n = p.next;
                if (n == null) {
                    return;
                }
                if (n.when > now) {
                    break;
                }
                p = n;
            }
            p.next = null;
            do {
                p = n;
                n = p.next;
                p.recycleUnchecked();
            } while (n != null);
        }
    }
}
複製程式碼

三、總結

  • 如果經常要開啟執行緒,接著又是銷燬執行緒,這是很耗效能的,HandlerThread很好的解決了這個問題;
  • HandlerThread由於非同步操作是放在Handler的訊息佇列中的,所以是序列的,但只適合併發量較少的耗時操作。

相關文章