1. 程式人生 > >Android 進階15:HandlerThread 使用場景及原始碼解析

Android 進階15:HandlerThread 使用場景及原始碼解析

  • 眼睛困得要死,但今天的計劃不完成又怎麼能睡呢?明日復明日,明日何其多啊!

讀完本文你將瞭解:

為了避免 ANR,我們常常需要線上程中做耗時操作,然後把結果拋到主執行緒進行處理。

Android 提供了多種用於這種場景的元件,其中一種就是本篇文章要介紹的 HandlerThread。

HandlerThread 簡介

class LooperThread extends Thread {
    public Handler mHandler;

      public void run() {
          Looper.prepare();

          mHandler = new
Handler() { public void handleMessage(Message msg) { // 這裡處理訊息 } }; Looper.loop(); }

可以看到,非常繁瑣,一層套一層看著也不美觀。

HandlerThread 就是為了幫我們免去寫上面那樣的程式碼而生的。

官方文件對它的介紹:

HandlerThread 是一個包含 Looper 的 Thread,我們可以直接使用這個 Looper 建立 Handler。

有這麼神奇?去瞅瞅它的原始碼。

HandlerThread 原始碼

HandlerThread 原始碼非常簡單,看起來 so easy:

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

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }

    //也可以指定執行緒的優先順序,注意使用的是 android.os.Process 而不是 java.lang.Thread 的優先順序!
public HandlerThread(String name, int priority) { super(name); mPriority = priority; } // 子類需要重寫的方法,在這裡做一些執行前的初始化工作 protected void onLooperPrepared() { } //獲取當前執行緒的 Looper //如果執行緒不是正常執行的就返回 null //如果執行緒啟動後,Looper 還沒建立,就 wait() 等待 建立 Looper 後 notify public Looper getLooper() { if (!isAlive()) { return null; } synchronized (this) { while (isAlive() && mLooper == null) { //迴圈等待 try { wait(); } catch (InterruptedException e) { } } } return mLooper; } //呼叫 start() 後就會執行的 run() @Override public void run() { mTid = Process.myTid(); Looper.prepare(); //幫我們建立了 Looepr synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); //Looper 已經建立,喚醒阻塞在獲取 Looper 的執行緒 } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); //開始迴圈 mTid = -1; } 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; } public int getThreadId() { return mTid; } }

可以看到,①HandlerThread 本質還是個 Thread,建立後別忘了呼叫 start()

②在 run() 方法中建立了 Looper,呼叫 onLooperPrepared 後開啟了迴圈

③我們要做的就是在子類中重寫 onLooperPrepared,做一些初始化工作

④在建立 HandlerThread 時可以指定優先順序,注意這裡的引數是 Process.XXX 而不是 Thread.XXX

Process.setThreadPriority(int)
A Linux priority level, from -20 for highest scheduling priority to 19 for lowest scheduling priority.

可選的值如下:

public static final int THREAD_PRIORITY_DEFAULT = 0;
public static final int THREAD_PRIORITY_LOWEST = 19;
public static final int THREAD_PRIORITY_BACKGROUND = 10;
public static final int THREAD_PRIORITY_FOREGROUND = -2;
public static final int THREAD_PRIORITY_DISPLAY = -4;
public static final int THREAD_PRIORITY_URGENT_DISPLAY = -8;
public static final int THREAD_PRIORITY_AUDIO = -16;

HandlerThread 的使用場景

我們知道,HandlerThread 所做的就是在新開的子執行緒中建立了 Looper,那它的使用場景就是 Thread + Looper 使用場景的結合,即:在子執行緒中執行耗時的、可能有多個任務的操作

比如說多個網路請求操作,或者多檔案 I/O 等等。

使用 HandlerThread 的典型例子就是 IntentService,我們下篇文章介紹它。

舉個栗子

我們寫一個使用 HandlerThread 實現子執行緒完成多個下載任務的 demo。

先建立一個 HandlerThread 子類,它有兩個 Handler 型別的成員變數,一個是用於在子執行緒傳遞、執行任務,另一個用於外部傳入,在主執行緒顯示下載狀態:

/**
 * Description:
 * <br> 繼承 HandlerThread 模擬下載執行緒
 * <p>
 * <br> Created by shixinzhang on 17/6/7.
 * <p>
 * <br> Email: [email protected]
 * <p>
 * <a  href="https://about.me/shixinzhang">About me</a>
 */

public class DownloadThread extends HandlerThread implements Handler.Callback {

    private final String TAG = this.getClass().getSimpleName();
    private final String KEY_URL = "url";
    public static final int TYPE_START = 1;
    public static final int TYPE_FINISHED = 2;

    private Handler mWorkerHandler;
    private Handler mUIHandler;
    private List<String> mDownloadUrlList;

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

    @Override
    protected void onLooperPrepared() {    //執行初始化任務
        super.onLooperPrepared();
        mWorkerHandler = new Handler(getLooper(), this);    //使用子執行緒中的 Looper
        if (mUIHandler == null) {
            throw new IllegalArgumentException("Not set UIHandler!");
        }

        //將接收到的任務訊息挨個新增到訊息佇列中
        for (String url : mDownloadUrlList) {
            Message message = mWorkerHandler.obtainMessage();
            Bundle bundle = new Bundle();
            bundle.putString(KEY_URL, url);
            message.setData(bundle);
            mWorkerHandler.sendMessage(message);
        }
    }

    public void setDownloadUrls(String... urls) {
        mDownloadUrlList = Arrays.asList(urls);
    }

    public Handler getUIHandler() {
        return mUIHandler;
    }

    //注入主執行緒 Handler
    public DownloadThread setUIHandler(final Handler UIHandler) {
        mUIHandler = UIHandler;
        return this;
    }

    /**
     * 子執行緒中執行任務,完成後傳送訊息到主執行緒
     *
     * @param msg
     * @return
     */
    @Override
    public boolean handleMessage(final Message msg) {
        if (msg == null || msg.getData() == null) {
            return false;
        }

        String url = (String) msg.getData().get(KEY_URL);


        //下載開始,通知主執行緒
        Message startMsg = mUIHandler.obtainMessage(TYPE_START, "\n 開始下載 @" + DateUtils.getCurrentTime() + "\n" + url);
        mUIHandler.sendMessage(startMsg);

        SystemClock.sleep(2000);    //模擬下載

        //下載完成,通知主執行緒
        Message finishMsg = mUIHandler.obtainMessage(TYPE_FINISHED, "\n 下載完成 @" + DateUtils.getCurrentTime() + "\n" + url);
        mUIHandler.sendMessage(finishMsg);

        return true;
    }

    @Override
    public boolean quitSafely() {
        mUIHandler = null;
        return super.quitSafely();
    }
}

可以看到,DownloadThread 中做了以下工作:

  • 建立一個子執行緒 Handler
  • 然後在 onLooperPrepared()中初始化 Handler,使用的是 HandlerThread 建立的 Looper
    • 同時將外部傳入的下載 url 以 Message 的方式傳送到子執行緒中的 MessageQueue
  • 這樣當呼叫 DownloadThread.start() 時,子執行緒中的 Looper 開始工作,會按順序取出訊息佇列中的佇列處理,然後呼叫子執行緒的 Handler 處理
  • 也就是上面的 handleMessage() 方法,在這個方法中進行耗時任務
  • 然後通過 mUIHandler 將下載狀態資訊傳遞到主執行緒

呼叫 Activity 的程式碼:

/**
 * Description:
 * <br> HandlerThread 示例程式
 * <p>
 * <br> Created by shixinzhang on 17/6/7.
 * <p>
 * <br> Email: [email protected]
 * <p>
 * <a  href="https://about.me/shixinzhang">About me</a>
 */

public class HandlerThreadActivity extends AppCompatActivity implements Handler.Callback {

    @BindView(R.id.tv_start_msg)
    TextView mTvStartMsg;
    @BindView(R.id.tv_finish_msg)
    TextView mTvFinishMsg;
    @BindView(R.id.btn_start_download)
    Button mBtnStartDownload;

    private Handler mUIHandler;
    private DownloadThread mDownloadThread;

    @Override
    protected void onCreate(@Nullable final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler_thread_test);
        ButterKnife.bind(this);
        init();
    }

    private void init() {
        mUIHandler = new Handler(this);
        mDownloadThread = new DownloadThread("下載執行緒");
        mDownloadThread.setUIHandler(mUIHandler);
        mDownloadThread.setDownloadUrls("http://pan.baidu.com/s/1qYc3EDQ",
                            "http://bbs.005.tv/thread-589833-1-1.html", "http://list.youku.com/show/id_zc51e1d547a5b11e2a19e.html?");
    }

    @OnClick(R.id.btn_start_download)
    public void startDownload() {
        mDownloadThread.start();
        mBtnStartDownload.setText("正在下載");
        mBtnStartDownload.setEnabled(false);
    }

    //主執行緒中的 Handler 處理訊息的方法
    @Override
    public boolean handleMessage(final Message msg) {
        switch (msg.what) {
            case DownloadThread.TYPE_FINISHED:
                mTvFinishMsg.setText(mTvFinishMsg.getText().toString() + "\n " + msg.obj);
                break;
            case DownloadThread.TYPE_START:
                mTvStartMsg.setText(mTvStartMsg.getText().toString() + "\n " + msg.obj);
                break;
        }
        return true;
    }
}

佈局檔案:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:gravity="center_horizontal"
              android:orientation="vertical"
              android:padding="8dp">

    <TextView
        android:id="@+id/tv_start_msg"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:text="下載開始資訊"/>

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="@color/colorAccent"/>

    <TextView
        android:id="@+id/tv_finish_msg"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_marginTop="8dp"
        android:text="下載完成資訊"/>

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:background="@color/colorAccent"/>

    <Button
        android:id="@+id/btn_start_download"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="開始下載"/>
</LinearLayout>

重點是 init() 方法(其中有福利,你懂得):

    private void init() {
        mUIHandler = new Handler(this);
        mDownloadThread = new DownloadThread("下載執行緒");
        mDownloadThread.setUIHandler(mUIHandler);
        mDownloadThread.setDownloadUrls("http://pan.baidu.com/s/1qYc3EDQ",
                            "http://bbs.005.tv/thread-589833-1-1.html", "http://list.youku.com/show/id_zc51e1d547a5b11e2a19e.html?");
    }

在這個方法中我們建立一個 DownloadThread,也就是 HandlerThread,然後傳入 UI 執行緒中的 Handler。

最後在按鈕的點選事件中呼叫了 start() 方法。

執行結果

這裡寫圖片描述

總結

這裡寫圖片描述

上面的例子中 HandlerThread 配合一個主執行緒 Handler 完成了在子執行緒中序列執行任務,同時在主執行緒中反饋狀態的功能。

如果用一句話總結 HandlerThread 的特點:

  • 它就是一個幫我們建立 Looper 的執行緒,讓我們可以直接線上程中使用 Handler 來處理非同步任務。

程式碼地址

相關推薦

Android 15HandlerThread 使用場景原始碼解析

眼睛困得要死,但今天的計劃不完成又怎麼能睡呢?明日復明日,明日何其多啊! 讀完本文你將瞭解: 為了避免 ANR,我們常常需要線上程中做耗時操作,然後把結果拋到主執行緒進行處理。 Android 提供了多種用於這種場景的元件,其中一種就是本篇文章要

Android 9程序通訊之 AIDL 解析

讀完本文你將瞭解: 在 Android 進階7:程序通訊之 AIDL 的使用 中我們使用 AIDL 實現了跨程序的通訊,但是不清楚 AIDL 幫我們做了什麼。 AIDL 的本質是簡化我們 IPC 開發,它使用的是 Binder 機制,於是在上篇文章

android4step2Android音視訊處理——拍照功能實現應用

Camera有哪幾種使用場景? 呼叫系統相機 使用Camera API 使用Camera大致的流程 1、呼叫系統的相機實現拍照/儲存/顯示 AndroidManifest.xml中寫上需要的許可權 注意:android6.0之後需要動態申請許可權 &

Android筆記AIDL內部實現詳解 (二)

ucc == 筆記 null stack 直接 android 最好 public 接著上一篇分析的aidl的流程解析。知道了aidl主要就是利用Ibinder來實現跨進程通信的。既然是通過對Binder各種方法的封裝,那也可以不使用aidl自己通過Binder來實現跨進

Android 13幾種程序通訊方式的對比總結

讀完本文你將瞭解: RPC 是什麼 IDL 是什麼 IPC 是什麼 Android 幾種程序通訊方式 如何選擇這幾種通訊方式 Thanks RPC 是什麼 RPC 即 Remote Procedure Call (遠端過程呼叫) 是一種計算機通訊協議,它為我們定義了計算機 C 中的程式如何呼叫另

android3step1Android元件通訊——Service基礎

轉:https://www.jianshu.com/p/95ec2a23f300 Android Service使用詳解   轉:https://www.jianshu.com/p/4c798c91a613 Android Service兩種啟動方式詳解(總結版

android3step1Android元件通訊——事件框架匯流排Otto

事件框架匯流排Otto 一、Otto是什麼? Otto是基於Guava專案的Android系統的一個EventBus模式類庫,如果你在Android程式開發的過程中想要不同的元件之間進行有效的通訊可以使用這個庫。通過Otto庫可以降低程式之間的耦合性。 二、Otto&nbs

android3step1Android元件通訊——廣播接收者BroadCast

轉:https://www.jianshu.com/p/ca3d87a4cdf3 前言 BroadcastReceiver(廣播接收器),屬於 Android四大元件之一 Broadcast是一種廣泛應用在程式之間傳輸資訊的機制,BroadcastReceiver是對傳

android3step2Android App通訊——經典藍芽通訊

  Android經典藍芽案例   - 一、Android中藍芽裝置的使用 - 1.藍芽許可權 - 2.藍芽功能開啟 - 3.搜尋藍芽裝置 - 4.建立RFCOMM通道 - 5.藍芽裝置雙向資料傳輸 1.開啟藍芽許可權:And

android3step2Android App通訊——Https和Http通訊

 需要了解的知識  X.509數字證書的結構與解析 計算機網路:這是一份全面& 詳細 HTTP知識講解  Https 實戰 • 主要用到的API介紹 – HttpsURLConnection (HttpURLConn

android3step2Android App通訊——Socket通訊

改:https://www.jianshu.com/p/089fb79e308b 掌握 Android中Socket程式設計,包括TCP和UDP通訊協議,以及加密傳輸、身份認證的網路協議Https的相關知識。 先掃一下盲:什麼是觀察者模式  埠號IP等網路基礎

android3step2Android App通訊 ——埠號IP等網路基礎知識掃盲

網路操作基礎知識 一、IP 地址和埠號   1) IP 地址用於在網路中唯一標識一臺機器(通訊實體),是一個 32 位整數,通常 用 4 個 0-255 的十進位制數標識; 2)&nbs

android3step2Android App通訊 ——觀察者模式

觀察者模式 一、什麼是觀察者模式? 觀察者模式即 Observer Pattern。 觀察者模式的主旨是定義物件間的一種一(被 觀察者 Observable)對多(觀察者 Observer)的依賴關係,當一個物件的狀態發生改變 時,所有依賴於它的物件都得到通

android3step2Android App通訊——Android執行緒間通訊

Android進階:網路與資料儲存—步驟1:Android網路與通訊(第2小節:Handler) https://blog.csdn.net/qq_17846019/article/details/82906216 Android進階:網路與資料儲存—步驟1:Android網路與通訊(第3小

android3step2Android App通訊——AIDL實現遠端服務的通訊

安卓介面描述語言AIDL 全稱:Android Interface definition language  作用:程序間的通訊介面(實現兩個程序資料共享) IBinder僅限於同一個程序間的資料共享 定義轉:https://www.jianshu.com

android3step3Android 常用框架——Logger框架

  Log做什麼用? Android研發人員在除錯程式的時候,或多或少的會使用log來檢視程式執行狀態。 同時,系統也會通過log提示研發人員,系統需要處理的資訊,例如異常,系統警告。 程式執行狀態(多執行緒尤為重要)  Log的級別 &nb

android3step4Android 拓展學習——Gif介紹

GIF是什麼 GIF(圖形交換格式)的原義是“影象互換格式”,是CompuServe公司公司在1987年開發的影象檔案格式。 GIF檔案的資料,是一種基於LZW演算法的連續色調的無失真壓縮格式。其壓縮率一般在50%左右,它不屬於任何應用程式。 跨平臺 GIF的特點

android3step4Android Studio——NDK配置

課程目標 1,瞭解NDK Android NDK 2,完成環境搭建 3,能夠開發完整NDK專案 課程內容 1,初見Android NDK 2,開發環境搭建 3,Android NDK到底是什麼 4,Android NDK使用規範 一、

Python-Flask鉤子應用場景使用介紹

在正常執行的程式碼前中後,強行插入執行一段你想要實現的功能的程式碼,這種函式就叫做鉤子函式。鉤子函式就是等同於高速公路上的收費站,進高速之前給你一個卡,並檢查你是否超重。離開之前收你,也可以攔住你安檢一下。 一、基礎概念: request: Flask的請求上下文,包

Android3Activity原始碼分析(2) —— Activity啟動和銷燬流程(8.0)

上篇文章講述了app從啟動建立Activity呼叫onCreate,onStart, onResume方法,這篇文章講述一下Activity啟動的另一個切入點:startActivity方法,啟動Activity。 通過上一篇文章,我們總結一下: 1:A