天天看點

Android 多線程之HandlerThread

一、什麼是HandlerThread及特點

HandlerThread本質就是一個Thread,在内部建立了消息隊列機制(Looper、MessageQueue),具有消息循環的線程。

特點

  • HandlerThread本質上一個線程類,它繼承了Thread
  • HandlerThread與自己的内部Looper對象,可以進行loop循環
  • 通過擷取looper對象傳遞給Handler對象,并可以在Handler的handleMessage方法中執行異步任務
  • 與線程池重并發不同,handlerThread是一個串行隊列,背後隻有一個線程
  • 優點就是不會堵塞,減少對信性能的消耗,缺點是不能同時進行多任務處理,需要等待處理,效率較低。

二、HandlerThread使用方法

1.建立HandlerThread線程,并在構造方法内傳入線程名稱,可以是任意字元串

HandlerThread mHandlerThread = new HandlerThread("Handler_Thread");
           

2.啟動線程

3.建立異步Handler對象,并建構Callback消息循環機制

mchildHandler = new Handler(mHandlerThread.getLooper(), new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {

                //子線程内運作,此處不能更新UI
                Log.d(TAG, "handleMessage: "+msg.what+" 線程名:"+Thread.currentThread().getName());

//                mTvContent.setText(String.valueOf(msg.what));
                //主線程Hanlder,發送消息通知更新UI
                mUIHandler.sendMessage(msg);

                return false;
            }
        });
           

以上步驟3建構一個完整異步Handler,将HandlerThread的Looper對接和Callback接口類作為參數傳遞到異步 mchildHandler,這樣目前的mchildHandler對象就擁有HandlerThread的Looper對象,由于HandlerThread本身是異步線程,故Looper也綁定到異步線程中,是以handleMessage方法是處理異步耗時任務,當處理狀态發生改變,需要UI線程的Handler發送消息通知更新UI。

mchildHandler = new Handler(mHandlerThread.getLooper(), new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {

                //子線程内運作,此處不能更新UI
                Log.d(TAG, "handleMessage: "+msg.what+" 線程名:"+Thread.currentThread().getName());

//                mTvContent.setText(String.valueOf(msg.what));
                //主線程Hanlder,通知主線程更新
                Message message = Message.obtain();
                message.what=msg.what;
                mUIHandler.sendMessage(message);

                return false;
            }
        });
           

模拟觸發異步線程

for (int i = ; i <= ; i++) {
                    Message obtain = Message.obtain();
                    obtain.what = i;
                    mchildHandler.sendMessageAtTime(obtain,);
                }
           

三、HandlerThread源碼分析

首先我們從構造函數開始:

public class HandlerThread extends Thread {
    //線程優先級
    int mPriority;
    int mTid = -;
    //目前線程所持有的Looper對象
    Looper mLooper;
    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是繼承于Thread,本質上是一個線程,構造方法需要傳入兩個參數,一個是name指的是線程名稱,一個是priority指的是線程的優先級,當建立了HandlerThread對象并開啟線程start方法後,run方法就會馬上執行,接下來看看run方法:

/**
     * 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對象
        Looper.prepare();
        synchronized (this) {
            //把初始化的Looper對象指派給HandlerThread的内部mLooper
            mLooper = Looper.myLooper();
            //喚醒等待線程
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -;
    }
           

從源碼可以看出,當run方法被執行後,會調用Looper.prepare()建立一個Looper對象和MessageQueue對象并綁定了在目前線程(異步線程);這樣才可以Looper對象以參數的形式傳給Handler對象,進而確定Handler的hanldMessage方法是在子線程(異步線程)中執行的;接下來會把建立的Looper對象指派給HandlerThread的内部變量mLooper,并通過notifyAll方法喚醒等待線程,最後調用loop方法,中間有個onLooperPrepared方法,可以重寫此方法,也就是說在loop開啟循環之前,可以做一些其他的操作。有個疑問就是在loop開啟循環之前,為什麼要喚醒等待線程呢,接下來看看getLooper方法:

/**
     * This method returns the Looper associated with this thread. If this thread not been started
     * or for any reason isAlive() returns false, this method will return null. If this thread
     * has been started, this method will block until the looper has been initialized.  
     * @return The looper .
       傳回此線程關聯的Looper對象
     */
    public Looper getLooper() {
       //如果線程沒有開啟,則傳回null
         if (!isAlive()) {
            return null;
        }

        // If the thread has been started, wait until the looper has been created.
       //如果線程被開啟了,而且目前線程Looper為null時,此時會等待狀态,直到mLooper被初始化并指派後會被喚醒
         synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }
           

當外部主線程調用getLooper方法,先會判斷目前異步線程是否啟動了,如果isAlive()為false ,說明線程未開啟,會傳回null,如果線程啟動了,則會進入同步代碼塊内并判斷mLooper是否為null,也就是内部變量mLooper是否被指派,為null表示未指派尚未初始化,進入等待狀态,直到mLooper被指派後,notifyAll方法會喚醒是以的等待線程 ,最後傳回Looper對象。 之是以有個等待喚醒機制,主要getLooper方法是在主線程調用的,而mLooper是在子線程初始化指派的,也就是我們無法保證不同線程通路同一個對象 是否被建立了,也就是說在調用getLooper方法來擷取Looper對象時,無法確定mLooper已經被建立了 ,,故引入等待喚醒機制來確定線程建立開啟後 并且Looper對象 也被建立後才可以成功傳回mLooper

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

線程的退出提供兩個方法,一個quit,一個quitSafely,quitSafely相對更安全下。

  • quit:會清除MessageQueue中全部的消息,無論是延遲還是非延遲的消息都清除。
  • quitSafely:該方法隻會清空MessageQueue消息池中所有的延遲消息,并将消息池中所有的非延遲消息派發出去讓Handler去處理完成後才停止Looper循環。

繼續閱讀