瞭解HandlerThread這一篇就夠了

Fxymine4ever發表於2019-02-26

HandlerThread解析及應用

本文我們講解的是HandlerThread,可能有部分同學不瞭解或者沒用過這個類。原因很簡單,在如今大神們的框架面前,許多的原生類已經不為人所用了,然而早期開發者要想在子執行緒裡更新UI則需要在Thread裡建立一個Handler。

class MyThread : Thread() {
    private lateinit var mHandler: Handler

    override fun run() {
        super.run()
        Looper.prepare()

        mHandler = object : Handler() {
            override fun handleMessage(msg: Message?) {
                super.handleMessage(msg)
                if(msg?.what==0){
                    //...處理訊息
                }
            }
        }

        Looper.loop()
    }
}
複製程式碼

是不是認為很難受?每次要想在子執行緒裡面處理資訊必須每次在子執行緒裡建立Handler?

然而貼心的Google工程師為我們提供了一個自帶Handler的類,名叫
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.
 */
public class HandlerThread extends Thread {
    ...
}
複製程式碼
  • 由它的註釋可以看出,這是一個帶有LooperThread,這個Looper可以被用於建立Handler,請注意,start()這個方法仍然需要被呼叫。
  • 總結一下,這個類就是自帶Handler的執行緒類。

構造方法

    int mPriority;//優先順序
    int mTid = -1;
    Looper mLooper;//自帶的Looper
    private @Nullable Handler mHandler;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    
    /**
     * Constructs a HandlerThread.
     * @param name
     * @param priority The priority to run the thread at. The value supplied must be from 
     * {@link android.os.Process} and not from java.lang.Thread.
     */
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
複製程式碼
  • 這裡有兩個構造方法,一個HandlerThread(String name),一個HandlerThread(String name, int priority),我們可以自己設定執行緒的名字以及優先順序。注意!是Process裡的優先順序而不是Thread的。
    //這是可選的優先順序
    public static final int THREAD_PRIORITY_DEFAULT = 0;
    public static final int THREAD_PRIORITY_LOWEST = 19;
    public static final int THREAD_PRIORITY_BACKGROUND = 10;
    public static final int THREAD_PRIORITY_FOREGROUND = -2;
    public static final int THREAD_PRIORITY_DISPLAY = -4;
    public static final int THREAD_PRIORITY_URGENT_DISPLAY = -8;
    public static final int THREAD_PRIORITY_VIDEO = -10;
    public static final int THREAD_PRIORITY_AUDIO = -16;
    public static final int THREAD_PRIORITY_URGENT_AUDIO = -19;
    public static final int THREAD_PRIORITY_MORE_FAVORABLE = -1;
    public static final int THREAD_PRIORITY_LESS_FAVORABLE = +1;
複製程式碼

其他方法

    
    /*
        在Looper.loop()之前呼叫的方法,如需要配置可重寫
    */
    protected void onLooperPrepared() {
    }


    /*
        來自Thread的run方法,呼叫與start()之後    
    */
    @Override
    public void run() {
        mTid = Process.myTid();//執行緒id
        Looper.prepare();//建立子執行緒的Looper
        synchronized (this) {
            mLooper = Looper.myLooper();//獲取Looper例項
            notifyAll();// native方法,用於喚醒所有等待獲取Looper的執行緒
        }
        Process.setThreadPriority(mPriority);//設定執行緒優先順序
        onLooperPrepared();//上面方法的呼叫
        Looper.loop();//Looper開始輪詢
        mTid = -1;
    }
    
    /*
        獲取Looper
    */
    public Looper getLooper() {
        //判斷執行緒是否啟動
        if (!isAlive()) {
            return null;
        }
        
        synchronized (this) {
            while (isAlive() && mLooper == null) {//如果執行緒啟動,且Looper為空
                try {
                    wait();//使其等待直至獲取到Looper
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

    /*
        獲取Handler
    */
    @NonNull
    public Handler getThreadHandler() {
        if (mHandler == null) {
            mHandler = new Handler(getLooper());
        }
        return mHandler;
    }

    
    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;
    }

    
    public int getThreadId() {
        return mTid;
    }
複製程式碼
  • 註釋我已寫在程式碼上,在HandlerThread不使用時記得使用 getLooper().quit()來退出這個執行緒,但需要注意的是quit和quitSafely的區別,接下來我將用較短的篇幅介紹一下這兩個方法的區別。

quit()和quitSafely()的區別

HandlerThread:
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()quitSafely()方法。我們點開原始碼檢視,他們本質都是在呼叫MessageQueuequit方法,不同的是quit方法的引數不同:

Looper:

quitSafely:

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

quit:

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

如果大家對訊息機制有所瞭解,那麼一定知道MessageQueue是用於處理Message(訊息)的佇列,如果不知道的可以去了解一下。話不多說,接下來我們將深入MessageQueue瞭解一下quit(boolean safe)方法是何方神聖。

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

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

            if (safe) {//敲黑板!這就是quitSafely和quit的區別
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }
複製程式碼
  • 當我們在Looper中呼叫quitSafely()時,在MessageQueue裡呼叫的removeAllFutureMessagesLocked()方法
  • 當我們在Looper中呼叫quit()時,在MessageQueue裡呼叫的是removeAllMessagesLocked()方法
  • 而這兩個方法本質的區別就是在於remove訊息的範圍,下面是一段虛擬碼:
            if (safe) {
                只清空佇列中的延時訊息(通過postDelay傳送的),非延時訊息繼續派發,直到完成
            } else {
                移除所有訊息,包括延時訊息
            }
複製程式碼

所以說safe的區別就是是否清除所有訊息,這兩個方法的實現我們就不深究了,畢竟本文是為了講解HandlerThread而不是資料結構,若有興趣的可以去自行研究。

應用

  • IntentService的底層就是利用HandlerThread
  • 舉個例子

    Activity:

class MainActivity : AppCompatActivity() ,Handler.Callback{


    private lateinit var mUIHandler :Handler
    private lateinit var mDownloadThread: DownloadThread

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        init()
    }

    private fun init(){
        mUIHandler = Handler(this)

        val list = mutableListOf<String>()
        list.add("url1")
        list.add("url2")
        list.add("url3")

        mDownloadThread = DownloadThread("Download")
                        .setUrls(list)
                        .setUIHandler(mUIHandler)

        mDownloadThread.start()
        Log.d("test","開始下載")
    }

    override fun handleMessage(msg: Message?): Boolean {
        when(msg?.what){
            DownloadThread.START->{
                Log.d("test","Activity接收到了START資訊")
            }
            DownloadThread.FINISH->{
                Log.d("test","Activity接收到了FINISH資訊")
            }
        }
        return true
    }
}
複製程式碼

DownloadThread:

class DownloadThread(name: String?) : HandlerThread(name), Handler.Callback {
    private lateinit var mWorkHandler: Handler
    private lateinit var mUIHandler: Handler
    private lateinit var urls: List<String>

    companion object {
        const val START = 1
        const val FINISH = 2
        const val KEY = "getUrl"
    }

    fun setUIHandler(mUIHandler: Handler): DownloadThread {
        this.mUIHandler = mUIHandler
        return this
    }

    fun setUrls(urls: List<String>): DownloadThread {
        this.urls = urls
        return this
    }

    override fun onLooperPrepared() {
        super.onLooperPrepared()
        if (looper != null)
            mWorkHandler = Handler(looper, this)

        //在這裡傳送下載資訊
        urls.forEach { url->
            val message = mWorkHandler.obtainMessage()
            val bundle = Bundle()
            bundle.putString(KEY,url)
            message.data = bundle
            mWorkHandler.sendMessage(message)
        }
    }


    //mWorkHandler的handleMessage
    override fun handleMessage(msg: Message?): Boolean {
        if (msg == null || msg.data == null) return false

        val url = msg.data.get(KEY) as String//獲取url

        val startMessage: Message = mUIHandler.obtainMessage(START)
        mUIHandler.sendMessage(startMessage)
        Log.d("test","$url :Thread傳送START資訊")

        Thread.sleep(2000)
        Log.d("test","$url :Thread執行耗時操作中...")

        val finishMessage: Message = mUIHandler.obtainMessage(FINISH)
        mUIHandler.sendMessage(finishMessage)
        Log.d("test","$url :Thread傳送FINISH資訊")
        return true
    }
}
複製程式碼

結果:

2019-01-30 20:47:47.743  D/test: 開始下載

2019-01-30 20:47:47.744  D/test: url1 :Thread傳送START資訊
2019-01-30 20:47:47.776  D/test: Activity接收到了START資訊
2019-01-30 20:47:49.746  D/test: url1 :Thread執行耗時操作中...
2019-01-30 20:47:49.747  D/test: url1 :Thread傳送FINISH資訊
2019-01-30 20:47:49.747  D/test: Activity接收到了FINISH資訊

2019-01-30 20:47:49.747  D/test: url2 :Thread傳送START資訊
2019-01-30 20:47:49.747  D/test: Activity接收到了START資訊
2019-01-30 20:47:51.748  D/test: url2 :Thread執行耗時操作中...
2019-01-30 20:47:51.749  D/test: url2 :Thread傳送FINISH資訊
2019-01-30 20:47:51.750  D/test: Activity接收到了FINISH資訊

2019-01-30 20:47:51.749  D/test: url3 :Thread傳送START資訊
2019-01-30 20:47:51.750  D/test: Activity接收到了START資訊
2019-01-30 20:47:53.750  D/test: url3 :Thread執行耗時操作中...
2019-01-30 20:47:53.751  D/test: url3 :Thread傳送FINISH資訊
2019-01-30 20:47:53.751  D/test: Activity接收到了FINISH資訊
複製程式碼
總結

上面就是利用HandlerThread在子執行緒中執行序列任務,並反饋到主執行緒的栗子。總的來說HandlerThread的知識點就是這些,若有錯誤或者遺漏歡迎指出。

相關文章