Thread、Handler和HandlerThread關係何在?

SillyMonkey發表於2019-03-29

前言

前幾天看到一道面試題:Thread、Handler和HandlerThread有什麼區別?,這個題目有點意思,對於很多人來說,可能對Thread和Handler很熟悉,主要涉及到Android的訊息機制(Handler、Message、Looper、MessageQueue),詳見《 從Handler.post(Runnable r)再一次梳理Android的訊息機制(以及handler的記憶體洩露)》

但是這個HandlerThread是拿來做什麼的呢?它是Handler還是Thread?我們知道Handler是用來非同步更新UI的,更詳細的說是用來做執行緒間的通訊的,更新UI時是子執行緒與UI主執行緒之間的通訊。那麼現在我們要是想子執行緒與子執行緒之間的通訊要怎麼做呢?當然說到底也是用Handler+Thread來完成(不推薦,需要自己操作Looper),Google官方很貼心的幫我們封裝好了一個類,那就是剛才說到的:HandlerThread。(類似的封裝對於多執行緒的場景還有AsyncTask

使用方法

還是先來看看HandlerThread的使用方法: 首先新建HandlerThread並且執行start()

private HandlerThread mHandlerThread;
......
mHandlerThread = new HandlerThread("HandlerThread");
handlerThread.start();
複製程式碼

建立Handler,使用mHandlerThread.getLooper()生成Looper:

        final Handler handler = new Handler(mHandlerThread.getLooper()){
            @Override
            public void handleMessage(Message msg) {
                System.out.println("收到訊息");
            }
        };
複製程式碼

然後再新建一個子執行緒來傳送訊息:

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);//模擬耗時操作
                    handler.sendEmptyMessage(0);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
複製程式碼

最後一定不要忘了在onDestroy釋放,避免記憶體洩漏:

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandlerThread.quit();
    }
複製程式碼

執行結果很簡單,就是在控制檯列印字串:收到訊息

原理

整個的使用過程我們根本不用去關心Handler相關的東西,只需要傳送訊息,處理訊息,Looper相關的東西交給它自己去處理,還是來看看原始碼它是怎麼實現的,先看構造方法:

public class HandlerThread extends Thread {}
複製程式碼

HandlerThread其實還是一個執行緒,它跟普通執行緒有什麼不同?

public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    ......
}
複製程式碼

答案是多了一個Looper,這個是子執行緒獨有的Looper,用來做訊息的取出和處理。繼續看看HandlerThread這個執行緒的run方法:

    protected void onLooperPrepared() {
    }
    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();//生成Looper
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();//空方法,在Looper建立完成後呼叫,可以自己重寫邏輯
        Looper.loop();//死迴圈,不斷從MessageQueue中取出訊息並且交給Handler處理
        mTid = -1;
    }
複製程式碼

主要就是做了一些Looper的操作,如果我們自己使用Handler+Thread來實現的話也要進行這個操作,再來看看getLooper()方法:

    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }
複製程式碼

方法很簡單,就是加了個同步鎖,如果已經建立了(isAlive()返回true)但是mLooper為空的話就繼續等待,直到mLooper建立成功,最後看看quit方法,值得一提的是有兩個:

    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;
    }
複製程式碼

quitSafely是針對在訊息佇列中還有訊息或者是延遲傳送的訊息沒有處理的情況,呼叫這個方法後都會被停止掉。

總結

HandlerThread的使用方法還是比較簡單的,但是我們要明白一點的是:如果一個執行緒要處理訊息,那麼它必須擁有自己的Looper,並不是Handler在哪裡建立,就可以在哪裡處理訊息的。

如果不用HandlerThread的話,需要手動去呼叫Looper.prepare()和Looper.loop()這些方法。

相關文章