詳解Android Handler的使用
Handler
概要
Handler用于線程間的消息傳遞,它可以將一個線程中的任務切換到另一個線程執(zhí)行。切換的目標線程與Handler內(nèi)部持有的Looper所在線程一致。若初始化Handler時未手動設置Looper,Handler會通過ThreadLocal獲取并持有當前(初始化Handler時)線程的Looper。當Handler發(fā)送一條消息后,這條消息會進入目標線程的MessageQueue,目標線程的Looper掃描并且取出消息,最終由Handler執(zhí)行這條消息。

構造器
Handler的構造器大致分為以下兩種:
public Handler(Callback callback, boolean async){}
public Handler(Looper looper, Callback callback, boolean async){}
構造器的參數(shù)列表:
- callback:Handler處理消息的接口回調(diào),執(zhí)行消息時可能會調(diào)用該接口。
- async:默認false,若該值為true,則消息隊列中的所有消息均是AsyncMessage。AsyncMessage的概念請看后續(xù)章節(jié)。
- looper:消息的查詢者,會不斷輪詢檢查MessageQueue是否有消息。
若調(diào)用者傳遞Looper,直接使用該Looper;否則通過ThreadLocal從當前線程中獲取Looper。所以執(zhí)行任務所在的目標線程不是創(chuàng)建Handler時所在的線程,而是Looper所在的線程。
sendMessageAtTime
無論是使用post(Runnable r)還是sendMessage(Message m)發(fā)送消息,最終都會執(zhí)行到sendMessageAtTime方法。該方法指定了Message的執(zhí)行者(msg.target=handler)和調(diào)用時機(msg.when)。
dispatchMessage
dispatchMessage方法用于執(zhí)行事先注冊的Message和Handler回調(diào),源碼如下:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
可以發(fā)現(xiàn)回調(diào)的優(yōu)先級是:Message的回調(diào)>Handler的回調(diào)(構造器章節(jié)中的callback)>Handler子類重寫的handleMessage方法。
ThreadLocal
ThreadLocal是一個線程內(nèi)部的數(shù)據(jù)存儲類,用于存放以線程為作用域的數(shù)據(jù),在不同的線程中可以持有不同的數(shù)據(jù)副本。通過ThreadLocal就可以很方便的查找到當前線程的Looper。ThreadLocal內(nèi)部實現(xiàn)的UML類圖如下:

通過ThreadLocal查找Looper的流程如下:
- 通過Thread.currentThread()獲取當前線程對象。
- 取出線程對象持有的ThreadLocalMap對象。
- 以自身為key,獲取ThreadLocalMap中對應Entry的value。
Looper
Looper在Handler中扮演著消息循環(huán)的角色。它會不斷查詢MessageQueue中是否有消息。當沒有消息時Looper將一直阻塞。
若當前線程沒有Looper,且調(diào)用者未傳Looper,Handler會因為未獲取Looper而報錯。解決辦法是通過Looper.prepare在當前線程手動創(chuàng)建一個Looper,并通過Looper.loop開啟消息循環(huán):
new Thread("Thread#2") {
@override
public void run() {
Looper.prepare();
Handler handler = new Handler();
Looper.loop();
}
}
Looper提供了quit和quitSafely兩種方式來退出一個Looper。區(qū)別在于前者會直接退出;后者則是在處理完消息隊列的已有消息后才安全退出。
Looper所在的線程會一直處于運行狀態(tài),所以建議消息處理完畢后及時退出Looper,釋放線程。
MessageQueue
MessageQueue是消息的存儲隊列,內(nèi)部提供了很多精彩的機制。
IdleHandler
IdleHandler本質(zhì)上只是一個抽象的回調(diào)接口,沒有做任何操作:
/**
* Callback interface for discovering when a thread is going to block
* waiting for more messages.
*/
public static interface IdleHandler {
/**
* Called when the message queue has run out of messages and will now
* wait for more. Return true to keep your idle handler active, false
* to have it removed. This may be called if there are still messages
* pending in the queue, but they are all scheduled to be dispatched
* after the current time.
*/
boolean queueIdle();
}
看上述注釋可以了解,MessageQueue會在將要進入阻塞時執(zhí)行IdleHandler的queueIdle方法,隊列阻塞的觸發(fā)時機是:
- 消息隊列沒有消息。
- 隊首消息的執(zhí)行時間大于當前時間。
當我們希望一個任務在隊列下次將要阻塞時調(diào)用,就可以使用IdleHandler。在Android工程中最常見的例子就是:給Activity提供生命周期以外的回調(diào)。
比如我希望在布局繪制完成后執(zhí)行某個操作,但是Activity的onStart和onResume回調(diào)均在View繪制完成之前執(zhí)行,可以看看onResume的官方注釋:
/**
* ...
* <p>Keep in mind that onResume is not the best indicator that your activity
* is visible to the user; a system window such as the keyguard may be in
* front. Use {@link #onWindowFocusChanged} to know for certain that your
* activity is visible to the user (for example, to resume a game).
* ...
*/
@CallSuper
protected void onResume() {...}
這種情況下就可以給MessageQueue設置一個IdleHandler,等當前隊列中的消息(包括繪制任務)執(zhí)行完畢并將要進入阻塞狀態(tài)時,調(diào)用IdleHandler的任務,確保任務在繪制結束后執(zhí)行。
使用方式如下所示:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Looper.myLooper().getQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
// do something when queue is idle
// 返回值表示bKeepAlive標識:true->繼續(xù)使用,false->銷毀該Handler
return false;
}
});
}
AsyncMessage和SyncBarrier
顧名思義,SyncBarrier表示同步柵欄(也叫作障礙消息),用于阻塞SyncMessage,優(yōu)先執(zhí)行AsyncMessage。該機制大大提升了MessageQueue的操作靈活性。
在進一步了解這兩個概念之前,需要先了解MessageQueue插入消息的機制,MessageQueue的enqueueMessage源碼如下(省略了喚醒隊列的相關代碼):
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
msg.markInUse();
msg.when = when;
Message p = mMessages;
if (p == null || when == 0 || when < p.when) {
// New head.
msg.next = p;
mMessages = msg;
} else {
// Inserted within the middle of the queue.
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
return true;
}
從上述源碼可知,消息按照調(diào)用時機(when)有序排列,當when等于0時,直接將消息插在隊頭;當when等于隊列中消息的when時,將消息插在這些消息的后方。
假設這樣一個場景:我們有一個非常緊急的任務,希望能夠優(yōu)先執(zhí)行,該如何處理?
很簡單,發(fā)送一個when為0的消息,它將自動被插到列表的頭部。Handler中也提供了現(xiàn)成的接口:
public final boolean postAtFrontOfQueue(Runnable r)
{
return sendMessageAtFrontOfQueue(getPostMessage(r));
}
public final boolean sendMessageAtFrontOfQueue(Message msg) {
return enqueueMessage(queue, msg, 0);
}
將場景升級一下:我們有一個任務A,其他所有任務都依賴于A,若A未執(zhí)行,則其他所有任務都不允許執(zhí)行。
A插入隊列的時間和執(zhí)行時間都是不確定的,在此之前,所有任務都不允許執(zhí)行。按照當前的機制無法實現(xiàn)該需求,此時SyncBarrier和AsyncMessage就派上了用場,實現(xiàn)流程如下:
- 調(diào)用MessageQueue.postSyncBarrier將SyncBarrier插入隊列:SyncBarrier本質(zhì)上是一個target為空的消息,插入邏輯和普通消息一致,也是按照when確定插入位置。SyncBarrier的when固定是SystemClock.uptimeMillis(),因此將其插入到隊列的中間(SyncBarrier前面可能會有一些無時延的消息,后面可能會有帶時延的消息)。
- 插入SyncBarrier后,輪詢消息直至SyncBarrier排到隊列頭節(jié)點,此時使用next方法查詢消息將自動過濾同步消息,只執(zhí)行異步消息。源碼如下所示:
// mMessages表示隊首消息
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
- 插入任務A(將A定義為AsyncMessage),由于SyncBarrier的存在,A將優(yōu)先被執(zhí)行(不排除A有時延,此時隊列將進入阻塞狀態(tài),即便隊列里可能存在無時延的同步消息)。
- 只要SyncBarrier放在隊首,同步消息將一直被阻塞,消息隊列只能輸出AsyncMessage。當任務A執(zhí)行完畢后,需要調(diào)用removeSyncBarrier手動將SyncBarrier移除。
Handler提供了接口讓我們插入AsyncMessage,即構造器中的asyc參數(shù)。當async為true時,所有通過Handler傳遞的消息均會被定義為AsyncMessage(前提是要和SyncBarrier配合使用,不然AsyncMessage沒有效果)。
再重新思考SyncBarrier和AsyncMessage機制的應用場景,本質(zhì)上就是為了阻塞從Barrier消息到AsyncMessage消息之間的同步消息的執(zhí)行。
在Android源碼中,布局的繪制就使用了這種機制。在ViewRootImpl的scheduleTraversals方法中,會事先往主線程的消息隊列設置Barrier,再去提交AsyncMessage,阻塞在此期間的所有同步消息。源碼如下:
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 設置Barrier
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 該方法最終會提交一個AsyncMessage
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
Tips:關于Barrier的概念在Java并發(fā)中多有涉及,比如CountDownLatch、CyclicBarrier等。詳情請查看《Thinking in Java》21.7章節(jié)。
阻塞和喚醒機制
阻塞和喚醒機制是MessageQueue的精髓,極大降低了Loop輪詢的頻率,減少性能開銷。
在IdleHandler章節(jié)已經(jīng)提及MessageQueue阻塞的時機:
消息隊列沒有消息。
隊首消息的執(zhí)行時間大于當前時間。
next方法的源碼如下:
Message next() {
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// 關鍵方法,將線程阻塞nextPollTimeoutMillis毫秒,若nextPollTimeoutMillis為-1,線程將一直處于阻塞狀態(tài)。
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Ignore SyncBarrier code
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Ignore IdleHandler code
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
}
}
}
插入消息時喚醒MessageQueue的時機(假設隊列處于阻塞狀態(tài)):
- 隊首插入一條SyncMessage。
- 隊首是一個柵欄,且插入一條離柵欄最近的AsyncMessage。
enqueueMessage方法的源碼如下:
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
// 關鍵方法,用于喚醒隊列線程
nativeWake(mPtr);
}
}
return true;
}
喚醒的第二種時機特意強調(diào)了插入離Barrier最近的AsyncMessage。對于如下的阻塞情況,插入AsyncMessage時不需要將其喚醒:

Handler內(nèi)存泄漏分析
了解了Handler的內(nèi)部原理后,再來分析由Handler引起的內(nèi)存泄露問題:
- 當定義了一個非靜態(tài)的Handler內(nèi)部類時,內(nèi)部類會隱式持有外圍類的引用。
- Handler執(zhí)行sendMessageAtTime方法時,Message的target參數(shù)會持有Handler對象。
- 當Message沒有被執(zhí)行時(比如now<when),若退出了Activity,此時Message依然持有Handler對象,而Handler持有Activity的對象,導致內(nèi)存泄露。
解決方案:
- 將Handler定義為靜態(tài)內(nèi)部類。
- 退出Activity時清空MessageQueue中對應的Message。
以上就是詳解Android Handler的使用的詳細內(nèi)容,更多關于Android Handler的資料請關注腳本之家其它相關文章!
相關文章
Android 網(wǎng)絡圖片查看顯示的實現(xiàn)方法
本篇文章小編為大家介紹,Android 網(wǎng)絡圖片查看顯示的實現(xiàn)方法,需要的朋友參考下2013-04-04
EditText限制小數(shù)點前后位數(shù)的實例
下面小編就為大家?guī)硪黄狤ditText限制小數(shù)點前后位數(shù)的實例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-04-04
Android Handle原理(Looper,Handler和Message)三者關系案例詳解
這篇文章主要介紹了Android Handle原理(Looper,Handler和Message三者關系案例詳解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-08-08

