Handler與異步消息處理

Handler 在 Android 中的應用很廣泛,基本上每個 Android 開發人員都會使用到它。本篇文章將會介紹 Handler 和異步消息機制相關的使用方法。

Android 中的異步消息處理框架由 Handler 、MessageQueue、Looper 和 ThreadLocal 等組成。Handler 是我們使用最多的一個類,主要負責發送和處理消息,MessageQueue 是一個隊列,用來存儲 Handler 發送來的消息,而 Looper 則是一個死循環。

Handler 的使用場景

由于 Android 系統不允許在主線程進行耗時任務,因此網絡請求等一般都會開新的線程執行,然而,Android 中的控件不是線程安全的,因此只能在主線程中訪問 UI 控件,否則就會報錯。那么從子線程中得到的數據怎么返回到主線程給 UI 使用呢?答案很簡單,大家都知道,可以使用 Handler 將數據返回到主線程。

但有時候你會發現,有些項目里面沒有開啟子線程,卻在使用 Handler,這就是 Handler 的另一個使用場景:消息處理。如果你有一個個的任務需要排隊處理,那么使用 Handler 是很合適的。

Handler 的使用

Handler 必須與一個 Looper 關聯才能使用,而且是在 Handler 創建的時候就要傳入一個 Looper 的。由于主線程中默認創建了一個 Looper,因此在主線程中不用傳入 Looper。如果不傳 Handler,那么系統就會去獲取當前線程的 Looper,如果找不到,就會報錯。因此,如果在子線程沒有手動創建 Looper 的情況下直接使用 Handler handler = new Handler() 的話,是會報錯的。

// 不傳入 Looper,系統會自己去獲取當前線程中的 Looper。
Handler handler = new Handler();

// 在子線程中,可以傳入自定的 Looper。
Handler  handler = new Handler(Looper);

每一個線程有且只能有一個 Looper,在創建 Looper 的時候,系統會檢查該線程是否已經有 Looper 對象了,已經有 Looper 對象了,則會報錯。

// 創建一個 Looper 很簡單。
Looper.prepare();

再次強調: Handler 必須和 Looper 相關聯才能使用 。一個 Handler 只能關聯一個 Looper,而且一旦關聯上了,就不能更改。當然,一個 Looper 可以關聯多個 Handler。

在子線程中創建 Handler

一般情況下,我們都是在主線程中創建 Handler,但是有時候也需要在子線程中處理消息隊列。由于主線程是唯一一個自帶了 Looper 的線程。因此在主線程中創建和使用 Handler 相對是比較簡單的,也是最常見的。但是還有些情況是需要在子線程中創建一個消息隊列的,在子線程中使用 Handler 需要提供一個 Looper,于是代碼就應該像下面那樣:

// 為異步消息處理框架準備一個線程
class LooperThread extends Thread {
    public Handler mHandler;
    public void run() {
        // 準備一個 Looper
        Looper.prepare();
        // Handler 對象會和該線程的 Looper 關聯起來。
        mHandler = new Handler() {
            public void handlerMessage(Message msg) {
                // 在這里處理傳入的消息
            }
        };
        // 使用該 Looper,啟動一個循環的消息隊列。
        Looper.loop();
    }
}

使用 HandlerThread

可以使用 HandlerThread 來簡化在子線程中創建 Handler 的流程。HalderThread 是一個自帶了 Looper 的線程類,

public class MyHandlerThread extends HandlerThread {
    // 你只需要添加一個 Handler
    private Handler handler;
    public MyHandlerThread(String name) {
        super(name)
    }
}

HandlerThread 也并不神秘,它只是幫你調用了 Looper.prepare() 方法和 Looper.loop() 方法而已。也就是說如果你一個類繼承了 ThreadHandler,你可以像在主線程那樣使用 Handler。

發送和處理消息

準備好 Handler 的環境后,就可以使用 Handler 來發送和處理消息了。處理消息是在 Handler 的 handleMessage() 方法中進行 的,而發送消息有兩種方法:發送一個 Message 對象,和投遞一個 Runnable 對象。下面分別介紹這兩類方法。

發送 Message 對象

Message 對象可以包含一些簡單的數據,并且可以通過 Handler 進行發送和處理。Message 對象可以通過 Message.what(int) 方法中的 int 參數來標志該對象。Message 對象使用兩個 int 類型的字段來存儲需要發送的信息,也可以使用一個 Object 類型的對象來存儲一個簡單對象的信息。

  • Message.what :標識一個 Message 對象。
  • Message.arg1 :需要傳遞的 int 類型的數據。
  • Message.arg2 :需要傳遞的 int 類型的數據。
  • Message.obj :存儲任意數據類型(Object)的對象。

怎么創建一個 Message 對象呢?你當然可以使用 Message msg = new Message() 方法來創建一個 Message 對象,但是不建議這么做。因為我們有更高效的方法來獲得 Message 對象:使用 Message msg = MMessage.obtain() 或 Handler.obtainMessage() 來獲取一個 Message。這個 Message 對象被 Handler 發送到 MessageQueue 之后,并不會被銷毀,可以重復利用,因此比使用 new 方法來創建一個 Message 對象效率更高。

當你需要將消息傳遞到一個后臺線程時,建議使用 Handler.obtainMessage 來創建一個 Message 對象:

int what = 0;
String hello = "Hello!";
// 獲取一個和后臺線程關聯的 Message 對象
Message msg = mHandler.obtainMessage(what, hello);
// 發送一個消息到后臺線程。
mHandler.sendMessage(msg);

sendMessage() 的相關方法

發送 Message 對象的方法同投遞一個 Runnable 對象的方法很類似:

  • Handler.sendMessage( Message msg ) :在 MessageQueue 中添加一個 Message 對象。
  • Handler.sendMessageAtFrontOfQueue( Message msg ) :添加一個 Message 對象到 MessageQueue 的前面。
  • Handler.sendMessageAtTime ( Message msg, long timeInMills ) :在指定的時間發送一個 Message 對象。
  • Handler.sendMessageDelayed( Message msg, long timeInMillis ) :在指定的時間之后,發送 Message 對象。

投遞一個 Runnable 對象

使用 Handler 發送任務的另一個方法就是投遞一個 Runnable 對象(“投遞”一詞,主要是翻譯 post() 方法)。創建一個 Runnable 對象必須要實現 run() 方法,因此,我們需要投遞執行的任務就要寫在 run() 方法中。

// 聲明一個 Runnable
Runnable r = new Runnable() {
    @Override
    public void run() {
        // 任務的具體內容
    }
}

在 Handler 中,有多種方式投遞一個 Runnable 對象:

  • Handler.post(Runnable r) :在 MessageQueue 中添加一個 Runnable 對象。
  • Handler.postAtFrontOfQueue :在 MessageQueue 的頭部添加一個 Runnable 對象。
  • Handler.postAtTime(Runnable r, long timeMillis) :在指定的時間將 Runnable 對象添加到 MessageQueue 中。
  • Handler.postDelay(Runnable r, long delay) :經過了指定的時間后,將 Runnable 對象添加到 MessageQueue 中。
// 投遞一個 Runnable 對象
Handler handler = new Handler();
handler.post(
    new Runnable() {
        @Override
        public void run() {
            // 任務的具體內容
        }
    });

當然,你也可以使用 Activity.runOnUiThread() 來投遞一個 Runnable 對象。

Activity.runOnUiThread (
    new Runnable() {
        @Override
        public void run() {
            // 任務的具體內容
        }
    });

該方法如果是在主線程上調用的,那么會立即執行。如果是在其他線程上使用的,那么就會將該 Runnable 對象投遞到主線程的消息隊列中去。

有一點需要記住, Runnable 對象和 Message 對象不同, Runnable 對象不能重復使用。

實例:倒計時

下面,我們用 Handler 來實現倒計時功能。先來分析一下 倒計時 的相關功能需求:

  • 需要知道總時間和每一步的間隔時間。
  • 每次到了間隔時間就回調通知,總時間結束后也要回調通知。

根據這個需求,我們可以大致猜想一下如何實現:

  • 需要提供兩個構造參數的構造方法,一個是總時間,一個是間隔時間。
  • 需要提供兩個抽象方法供子類實現,一個是每次到了間隔時間的回調通知,另一個是總時間結束后的回調通知。

有了這個思路,實現就很簡單了,下面直接給出代碼:

public abstract class Timer {

    private long mMillisInFuture;
    private long mCountdownInterval;
    private long mStopTimeInFuture;
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            // 計算剩余時間
            long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();
            if (millisLeft <= 0) {
                // 如果剩余時間小于或等于 0, 則回調 onFinish() 方法。
                onFinish();
            } else if (millisLeft < mCountdownInterval) {
                // 當剩余時間小于一次間隔時間時,不回調 onTick() 方法,直接等到最后再發送
                sendMessageDelayed(obtainMessage(), millisLeft);
            } else {
                // 回調 onTick() 方法,并在一次間隔時間后再次發送消息。
                onTick(millisLeft);
                sendMessageDelayed(obtainMessage(), mCountdownInterval);
            }
        }
    };

    public Timer(long millisInFuture, long countdownInterval) {
        mMillisInFuture = millisInFuture;
        mCountdownInterval = countdownInterval;
    }

    /**
     * 調用 start() 方法以啟動倒計時
     * @return
     */
    public Timer start() {
        if (mMillisInFuture < 0) {
            onFinish();
            return this;
        }
        // 獲取倒計時完成的系統時間
        mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;
        // 發送第一個消息
        mHandler.sendMessage(mHandler.obtainMessage());
        return this;
    }

    /**
     * 每經過一次間隔時間就回調一次 onTick 方法
     * @param millionLeft   剩余時間
     */
    abstract void onTick(long millionLeft);

    /**
     * 倒計時完成后,回調 onFinish() 方法
     */
    abstract void onFinish();
}

其實上面的代碼是 Android 系統中 CountDownTimer 類的簡化版, CountDownTimer 增加了線程安全機制并處理了一些特殊情況,有興趣的朋友可以自行查閱。

 

來自:http://wl9739.github.io/2016/10/31/Handler與異步消息處理/

 

 本文由用戶 VMQJesse 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!