1. 程式人生 > >Android HandlerThread 詳解

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 避免出現記憶體洩露

&nbs