Android HandlerThread 詳解

概述

HandlerThread 相信大家都比較熟悉了,從名字上看是一個帶有 Handler 消息循環機制的一個線程,比一般的線程多了消息循環的機制,可以說是 Handler + Thread 的結合,從源碼上看也是如此的設計,一般情況下如果需要子線程和主線程之間相互交互,可以用 HandlerThread 來設計,這比單純的 Thread 要方便,而且更容易管理,因為大家都知道Thread 的生命周期在一些情況下是不可控制的,比如直接 new Thread().start() 這種方式在項目中是不推薦使用的,實際上 Android 的源碼中也有很多地方用到了 HandlerThread,下面我將分析一下 HandlerThread 用法以及源碼解析。

使用示例

// 實例對象,參數為線程名字
HandlerThread handlerThread = new HandlerThread("handlerThread");
// 啟動線程
handlerThread.start();
// 參數為 HandlerThread 內部的一個 looper
    Handler handler = new Handler(handlerThread.getLooper()) {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

注意:這個使用的順序是不能更改的!!!,因為如果不先讓子線程 start 起來,那麼創建主線程的 handler 的參數 getLooper 是獲取不到的,這一點可以看源碼就清楚。

Demo 詳解

這裡模擬在子線程下載東西,然後和主線程之間進行通信。主線程知道了下載開始和下載結束的時間,也就能及時改變界面 UI。

首先是 DownloadThread 類,繼承於 HandlerThread,用於下載。

public class DownloadThread extends HandlerThread{

    private static final String TAG = "DownloadThread";

    public static final int TYPE_START = 2;//通知主線程任務開始
    public static final int TYPE_FINISHED = 3;//通知主線程任務結束

    private Handler mUIHandler;//主線程的Handler

    public DownloadThread(String name) {
        super(name);
    }

    /*
    * 執行初始化任務
    * */
    @Override
    protected void onLooperPrepared() {
        Log.e(TAG, "onLooperPrepared: 1.Download線程開始準備");
        super.onLooperPrepared();
    }

    //注入主線程Handler
    public void setUIHandler(Handler UIhandler) {
        mUIHandler = UIhandler;
        Log.e(TAG, "setUIHandler: 2.主線程的handler傳入到Download線程");
    }

    //Download線程開始下載
    public void startDownload() {
        Log.e(TAG, "startDownload: 3.通知主線程,此時Download線程開始下載");
        mUIHandler.sendEmptyMessage(TYPE_START);

        //模擬下載
        Log.e(TAG, "startDownload: 5.Download線程下載中...");
        SystemClock.sleep(2000);

        Log.e(TAG, "startDownload: 6.通知主線程,此時Download線程下載完成");
        mUIHandler.sendEmptyMessage(TYPE_FINISHED);
    }
} 

然後是 MainActivity 部分,UI 和處理消息。

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    private DownloadThread mHandlerThread;//子線程
    private Handler mUIhandler;//主線程的Handler

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //初始化,參數為線程的名字
        mHandlerThread = new DownloadThread("mHandlerThread");
        //調用start方法啟動線程
        mHandlerThread.start();
        //初始化Handler,傳遞mHandlerThread內部的一個looper
        mUIhandler = new Handler(mHandlerThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                //判斷mHandlerThread里傳來的msg,根據msg進行主頁面的UI更改
                switch (msg.what) {
                    case DownloadThread.TYPE_START:
                        //不是在這裡更改UI哦,只是說在這個時間,你可以去做更改UI這件事情,改UI還是得在主線程。
                        Log.e(TAG, "4.主線程知道Download線程開始下載了...這時候可以更改主界面UI");
                        break;
                    case DownloadThread.TYPE_FINISHED:
                        Log.e(TAG, "7.主線程知道Download線程下載完成了...這時候可以更改主界面UI,收工");
                        break;
                    default:
                        break;
                }
                super.handleMessage(msg);
            }
        };
        //子線程注入主線程的mUIhandler,可以在子線程執行任務的時候,隨時發送消息回來主線程
        mHandlerThread.setUIHandler(mUIhandler);
        //子線程開始下載
        mHandlerThread.startDownload();
    }

    @Override
    protected void onDestroy() {
        //有2種退出方式
        mHandlerThread.quit();
        //mHandlerThread.quitSafely(); 需要API>=18
        super.onDestroy();
    }
} 

運行的Log日誌如下

在這裡插入圖片描述 

源碼解析

先來看下構造函數相關的:

    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 的。
 /**
     * Call back method that can be explicitly overridden if needed to execute some
     * setup before Looper loops.
     */
    protected void onLooperPrepared() {
    }

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
這裏面有一個方法 onLooperPrepared(),在實際中,我們可以重寫這個方法做一些初始化的操作,這個 run() 是重點。

run 方法中首先獲取線程 id,然後就調用了 Looper.prepare 方法創建一個 Looper,接着調用了 Looper.myLooper 方法獲取到了當前線程的 Looper

接着通過 notifyAll 通知等帶喚醒,這裡的等待是在 HandlerThread 的 getLooper 方法里調用的 wait 方法,getLooper 方法是為了獲取該 HandlerThread 中的 Looper

如果在沒調用 HandlerThread 的 start 方法開啟線程前就調用 getLooper 方法就通過 wait 方法暫時先進入等待,等到 run 方法運行後再進行喚醒。喚醒之後 run 方法中繼續設置了構造函數中傳入的優先級,接着調用了onLooperPrepared 方法,該方法是個空實現,該方法是為了在 Looper 開啟輪詢之前如果要進行某些設置,可以複寫該方法。

最後調用Looper.loop開啟輪詢。退出的時候,將 mTid  = -1;

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

 這個方法是獲取當前的 Looper,可以看到如果沒有獲取的時候就一直等待直到獲取,而前面也提到了獲取到了就喚醒了所有的線程,看來這是線程的等待-喚醒機制應用。

public Handler getThreadHandler() {
        if (mHandler == null) {
            mHandler = new Handler(getLooper());
        }
        return mHandler;
    }

這個是獲取 HandlerThread 綁定的 Looper 線程的 Handler 

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 循環,那麼這兩個方法有什麼區別呢,實際上都是調用了 MessageQueue 的 quit() 方法,源碼如下:

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

可以看到: 當我們調用 quit 方法的時候,實際上執行了 MessageQueue 中的 removeAllMessagesLocked 方法,該方法的作用是把 MessageQueue 消息池中所有的消息全部清空,無論是延遲消息(延遲消息是指通過 sendMessageDelayed 或通過 postDelayed 等方法發送的需要延遲執行的消息,只要不是立即執行的消息都是延遲消息)還是非延遲消息。

而 quitSafely 方法時,實際上執行了 MessageQueue 中的 removeAllFutureMessagesLocked 方法,通過名字就可以看出,該方法只會清空 MessageQueue 消息池中所有的延遲消息,並將消息池中所有的非延遲消息派發出去讓 Handler 去處理,quitSafely 相比於 quit 方法安全之處在於清空消息之前會派發所有的非延遲消息,一句話,就是清除未來需要執行的消息。

這兩個方法有一個共同的特點就是:Looper 不再接收新的消息了,消息循環就此結束,此時通過 Handler 發送的消息也不會在放入消息杜隊列了,因為消息隊列已經退出了。應用這2個方法的時候需要注意的是:quit 方法從 API 1 就開始存在了,比較早,而 quitSafely 直到 API 18 才添加進來.

總結

  • 如果經常要開啟線程,接着又是銷毀線程,這是很耗性能的,HandlerThread 很好的解決了這個問題;

  • HandlerThread 由於異步操作是放在 Handler 的消息隊列中的,所以是串行的,但只適合併發量較少的耗時操作。

  • HandlerThread 用完記得調用退出方法。

  • 注意使用 handler 避免出現內存泄露

 
Tags: