1. 程式人生 > >Android/java多執行緒(三)-HandlerThread的使用場景及原始碼解析

Android/java多執行緒(三)-HandlerThread的使用場景及原始碼解析

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.

大意是說它是用來啟動具有一個內部迴圈的新執行緒的一個便利類。
內部迴圈?同時名稱又有Handler又有Thread,看過我這篇文章的同學估計猜到了,這貨估計是Handler與Thread的親兒子無疑了,但是兩個好基友哪來的兒子呢,囧~~~~
往期目錄:

Android/java 多執行緒(一)-Thread的使用以及原始碼分析
Android/java 多執行緒(二)-Thread的好兄弟Handler

我在上篇文章介紹Handler的時候舉了一個例子,在子執行緒中使用Handler:

    class MyThread extends Thread {

        @Override
        public void run() {
            Looper.prepare();
            @SuppressLint("HandlerLeak")
            Handler mHandler = new Handler(){
                @Override
                public void handleMessage(Message msg) {
                     //處理邏輯
                    l(Thread.currentThread().getName());
                }
            };
            Message message = mHandler.obtainMessage(1);
            mHandler.sendMessage(message);
            Looper.loop();
        }
    }

是不是顯得巨麻煩,而且巨醜,HandlerThread就是為了防止我們寫出如此醜陋的程式碼而生的,它的本質原理其實也就是以上那幾行程式碼。不信?,我們來瞅瞅

HandlerThread原始碼

public class HandlerThread extends Thread {
    int mPriority; //執行緒優先順序
    int mTid = -1; 
    Looper mLooper;
    private @Nullable Handler mHandler;

//指定執行緒的名稱,預設執行緒優先順序為DEFAULT,也是最低階的
    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    
//指定執行緒的名稱,同時指定執行緒的優先順序
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
    
    //這裡主要處理一些資料的初始化啊一些子執行緒的邏輯操作啥的,在run()方法中回撥
    protected void onLooperPrepared() {
    }


   //熟悉的run()方法
    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();  //建立Looper()
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();  //到這裡Looper已經建立完畢,喚醒阻塞的執行緒
        }
        //設定執行緒優先順序
        Process.setThreadPriority(mPriority);
        onLooperPrepared(); //執行onLooperPrepared()方法
        Looper.loop(); //開啟迴圈
        mTid = -1;
    }
    
    /**
     * 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.
     */
    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;
    }

    /**
     * @return a shared {@link Handler} associated with this thread
     * @hide
     */
    @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;
    }

    /**
     * Returns the identifier of this thread. See Process.myTid().
     */
    public int getThreadId() {
        return mTid;
    }
}

結合原始碼,這貨其實也就是在run()中開啟了Looper迴圈,並提供了一個onLooperPrepared ()方法供我們使用,做一些初始化的操作。
需要注意的是quit()方法和quitSafely()的區別,quit()是立即停止Looper迴圈,此時可能有訊息未處理就GG了,所以說它的不安全的,而quitSafely()是等待訊息處理完畢後才停止Looper迴圈,所以說它是安全的,另外可以看到它指定執行緒優先順序是使用的Process.setThreadPriority(mPriority)方法,所有它的可選值是從Process()裡面獲取

HandlerThread的使用場景

HandlerThread小朋友的優點

HandlerThread裡面的本質實際上是子執行緒訊息輪詢機制,我們能夠從中獲取到一個在子執行緒中輪詢的Looper,如果設定給Handler,那麼這個Handler就能在子執行緒中處理訊息。所以它特別適合處理大量需要排隊等待或需要重複操作的又耗時的邏輯,它既能夠方便的通知主執行緒更新UI,內部又能方便的做執行緒的佇列處理,所以它的使用還是比較廣泛滴。

舉個栗子

假設有這麼個需求:需要批量下載檔案,一次只能下載一個,下載完畢後自動下載下一個,或者這個下載失敗或暫停自動下載下一個(是不是有點像某雷的下載場景),偽實現如下:


/**
 * Created by hj on 2018/12/21.
 * 說明:模擬執行緒下載
 */
public class DownloadHandlerThread extends HandlerThread implements Handler.Callback {

    private Handler workHandler; //處理下載邏輯的Handler
    private Handler uiHandler; //處理UI重新整理的Handler

    public final static int READY = 0X110;
    public final static int STAR = 0X111;
    public final static int STOP = 0X112;
    public final static int ERROR = 0X113;


    private int max;
    private int index;

    public DownloadHandlerThread() {
        super("download-thread");
    }

    @Override
    protected void onLooperPrepared() {
        //初始化
        workHandler = new Handler(getLooper(), this);
        if (uiHandler == null) {
            throw new NullPointerException("uiHandler is not null");
        }
        uiHandler.sendEmptyMessage(READY); //通知主執行緒Looper已經準備完畢,可以開始下載了
    }

    @Override
    public boolean handleMessage(Message msg) {
        switch (msg.what) {
            case STAR: //開始下載
                index++; //記錄當前下載到了第幾個
                String url = msg.getData().getString("url");
                Log.i("HJ", "準備下載:" + url);
                try {
                    sleep(2000); //模擬下載
                    if (index == 2) {  //下載到第二個的時候模擬下載失敗
                        Log.i("HJ", "下載出錯:" + url);
                        setErrorMessage(url);
                        Log.i("HJ", "開始下載下一個地址");
                    } else {
                        Log.i("HJ", "當前地址下載完成:" + url);
                    }
                } catch (InterruptedException e) {
                    setErrorMessage(url);
                    e.printStackTrace();
                }
                if (index == max) { //全部下載完畢
                    workHandler.sendEmptyMessage(STOP);
                }
                break;
            case STOP: //全部下載完成
                
                //退出Looper迴圈
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
                    quitSafely();
                } else {
                    quit();
                }
                Log.i("HJ", "全部下載完成");
                break;
        }
        return false;
    }

    //傳送下載錯誤訊息到主執行緒用於重新整理UI
    private void setErrorMessage(String url) {
        Message errorMsg = new Message();
        errorMsg.what = ERROR;
        Bundle bundle = new Bundle();
        bundle.putString("errorUrl", url);
        errorMsg.setData(bundle);
        uiHandler.sendMessage(errorMsg);
    }

    public void setUiHandler(Handler uiHandler) {
        this.uiHandler = uiHandler;
    }

    public Handler getWorkHandler() {
        return workHandler;
    }

    public void setMax(int max) {
        this.max = max;
    }
}

在Activity中使用:

public class MainActivity extends AppCompatActivity {

    private Handler uiHandler; 
    //模擬資料來源
    private String[] urls = new String[]{
            "url-1", "url-2", "url-3", "url-4", "url-5"
    };
    private DownloadHandlerThread mThread;


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

    @SuppressLint("HandlerLeak")
    private void initUiHandler() {
        uiHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case DownloadHandlerThread.READY: //獲取到workHandler已經初始化完畢,可以進行下載任務了
                        Handler workHandler = mThread.getWorkHandler();
                        mThread.setMax(urls.length);
                        for (String url : urls) {  //迴圈將Message加入MessageQueue訊息池中
                            Message message = new Message();
                            message.what = DownloadHandlerThread.STAR;
                            Bundle bundle = new Bundle();
                            bundle.putString("url", url);
                            message.setData(bundle);
                            workHandler.sendMessage(message);
                        }
                        break;
                    case DownloadHandlerThread.ERROR:  //處理下載失敗UI邏輯
                        String url = msg.getData().getString("errorUrl");
                        showShortToast("下載失敗連結:" + url);
                        Log.i("HJ","展示下載失敗UI>>>>>>>>");
                        break;
                }

            }
        };
    }

    private void showShortToast(String string) {
        Toast.makeText(this, string, Toast.LENGTH_SHORT).show();
    }

    private void initDownLoad() {
        mThread = new DownloadHandlerThread();
        mThread.setUiHandler(uiHandler);
        mThread.start();
    }
}

執行結果如下:

2018-12-21 11:51:02.775 2390-2432/com.zj.example.customview.funnel I/HJ: 準備下載:url-1
2018-12-21 11:51:04.775 2390-2432/com.zj.example.customview.funnel I/HJ: 當前地址下載完成:url-1
2018-12-21 11:51:04.775 2390-2432/com.zj.example.customview.funnel I/HJ: 準備下載:url-2
2018-12-21 11:51:06.777 2390-2432/com.zj.example.customview.funnel I/HJ: 下載出錯:url-2
2018-12-21 11:51:06.777 2390-2432/com.zj.example.customview.funnel I/HJ: 開始下載下一個地址
2018-12-21 11:51:06.778 2390-2432/com.zj.example.customview.funnel I/HJ: 準備下載:url-3
2018-12-21 11:51:06.779 2390-2390/com.zj.example.customview.funnel I/HJ: 展示下載失敗UI>>>>>>>>
2018-12-21 11:51:08.778 2390-2432/com.zj.example.customview.funnel I/HJ: 當前地址下載完成:url-3
2018-12-21 11:51:08.778 2390-2432/com.zj.example.customview.funnel I/HJ: 準備下載:url-4
2018-12-21 11:51:10.779 2390-2432/com.zj.example.customview.funnel I/HJ: 當前地址下載完成:url-4
2018-12-21 11:51:10.779 2390-2432/com.zj.example.customview.funnel I/HJ: 準備下載:url-5
2018-12-21 11:51:12.783 2390-2432/com.zj.example.customview.funnel I/HJ: 當前地址下載完成:url-5
2018-12-21 11:51:12.783 2390-2432/com.zj.example.customview.funnel I/HJ: 全部下載完成

可以看到DownloadHandlerThread主要承載的功能如下:

  • 初始化一個子執行緒Handler用於處理子執行緒的訊息通訊
  • 執行下載任務以及與主執行緒的ui通訊

這樣的處理方式極大的方便了ui執行緒與子執行緒的通訊,只需要用不同的handler傳送訊息即可,來再多的handler都不怕。而且這隻需要開啟一個執行緒,極大的節省了記憶體空間,而且能讓執行緒任務有序進行,方便管理。

建議將以上程式碼複製過去跑一跑,除錯一下,這樣能更快的理解HandlerThread的通訊以及它的便捷通訊機制。