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與異步消息處理/