Handler應(yīng)該是Android開(kāi)發(fā)過(guò)程中使用最頻繁的類(lèi)了丹弱,但你真的理解Handler了嗎?本文深入剖析Handler內(nèi)部的實(shí)現(xiàn)機(jī)制咧党,以及分析使用過(guò)程中常出現(xiàn)的內(nèi)存泄漏的問(wèn)題呛谜。本文針對(duì)使用過(guò)Handler的用戶凌箕,沒(méi)有再介紹Handler的使用。
Handler的用途
與Handler的相識(shí)相知决侈,一般是通過(guò)子線程更新UI的Exception創(chuàng)造的機(jī)會(huì)螺垢。
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
Handler可以用于切換到主線程更新UI,但是它的作用絕不僅于此赖歌。源碼中Handler類(lèi)的注釋說(shuō)的很簡(jiǎn)單枉圃、明確。摘錄如下庐冯,翻譯水平有限:
A Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue. Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler, it is bound to the thread / message queue of the thread that is creating it -- from that point on, it will deliver messages and runnables to that message queue and execute them as they come out of the message queue.
Handler通過(guò)線程的MessageQueue來(lái)發(fā)送和處理Message孽亲、Runnable。每一個(gè)Handler實(shí)例關(guān)聯(lián)一個(gè)特定的線程和這個(gè)線程的消息隊(duì)列展父。當(dāng)創(chuàng)建一個(gè)新的Handler的時(shí)候返劲,它會(huì)綁定到創(chuàng)建它的線程的消息隊(duì)列赁酝,從這一刻起,該Handler就會(huì)分發(fā)messages和runnables到這個(gè)消息隊(duì)列旭等,并在他們出隊(duì)的時(shí)候執(zhí)行他們酌呆。
There are two main uses for a Handler: (1) to schedule messages and runnables to be executed as some point in the future; and (2) to enqueue an action to be performed on a different thread than your own.
Handler的主要用途有2個(gè):(1)延時(shí)執(zhí)行(在將來(lái)某個(gè)時(shí)間調(diào)度)messages和runnables;(2)切換線程來(lái)排隊(duì)處理一個(gè)動(dòng)作(action)搔耕。
歸結(jié)起來(lái)隙袁,Handler的用途有三個(gè)關(guān)鍵詞:延時(shí)、切換線程弃榨、排隊(duì)任務(wù)菩收。更新UI主要是應(yīng)用了Handler切換線程的功能(當(dāng)然此時(shí)的排隊(duì)處理也是附帶在其中的),排隊(duì)有時(shí)也是很重要的一個(gè)特性鲸睛,例如一種場(chǎng)景娜饵,某些后臺(tái)耗時(shí)任務(wù)需要順序執(zhí)行,此時(shí)就可以綁定一個(gè)Handler到子線程官辈,然后發(fā)送任務(wù)箱舞,這些任務(wù)就可以順序執(zhí)行了。
那么拳亿,Handler是如何實(shí)現(xiàn)延時(shí)和線程切換的呢晴股?延時(shí)是通過(guò)sleep的方式嗎?
Handler的內(nèi)部機(jī)制
我們從Handler的send**方法和post方法的調(diào)用開(kāi)始肺魁,順藤摸瓜电湘,來(lái)探究其內(nèi)部是如何實(shí)現(xiàn)線程切換和延時(shí)執(zhí)行的?
所有的send**方法和post**方法最終都會(huì)調(diào)用下面兩個(gè)方法中的一個(gè):
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
???? MessageQueue queue = mQueue;
??? if (queue == null) {
???????? RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");
???????? Log.w("Looper", e.getMessage(), e);
??????? return false;
??? }
??? return enqueueMessage(queue, msg, uptimeMillis);
}
public final boolean sendMessageAtFrontOfQueue(Message msg) {
???? MessageQueue queue = mQueue;
??? if (queue == null) {
??????? RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");
??????? Log.w("Looper", e.getMessage(), e);
??????? return false;
??? }
??? return enqueueMessage(queue, msg, 0);
}
可以看出這兩個(gè)方法幾乎是一樣的鹅经,最終都會(huì)調(diào)用enqueueMessage方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
??? msg.target = this;
??? if (mAsynchronous) {
???????? msg.setAsynchronous(true);
??? }
????return queue.enqueueMessage(msg, uptimeMillis);
}
enqueueMessage方法沒(méi)有做實(shí)際的工作寂呛,直接轉(zhuǎn)到了MessageQueue的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;
}
這就是把消息插入隊(duì)列贷痪,可以看到盡管MessageQueue叫做消息隊(duì)列,但是它的內(nèi)部實(shí)現(xiàn)是并不是隊(duì)列酗捌,而是一個(gè)單鏈表的數(shù)據(jù)結(jié)構(gòu)呢诬,mMessages就是鏈表的頭Head。上面的enqueueMessage就是實(shí)現(xiàn)了鏈表的插入操作胖缤,不需要做過(guò)多的解釋了尚镰。
現(xiàn)在消息已經(jīng)放在消息隊(duì)列中了,那么誰(shuí)會(huì)到消息隊(duì)列取消息呢哪廓?這里就不賣(mài)關(guān)子了狗唉,就是Handler中持有的Looper,Looper中的loop方法會(huì)一直不停的去消息隊(duì)列中取消息涡真,如下:
public static void loop() {
????? final Looper me = myLooper();
???? if (me == null) {
??????????throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
???? }
???? final MessageQueue queue = me.mQueue;
??? // Make sure the identity of this thread is that of the local process,
??? // and keep track of what that identity token actually is.
??? Binder.clearCallingIdentity();
??? final long ident = Binder.clearCallingIdentity();
??? for (;;) {
???????? Message msg = queue.next(); // might block
???????? if (msg == null) {
???????? // No message indicates that the message queue is quitting.
??????? return;
??? }
???? // This must be in a local variable, in case a UI event sets the logger
???? final Printer logging = me.mLogging;
???? if (logging != null) {
????????? logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what);
???? }
???? final long traceTag = me.mTraceTag;
???? if (traceTag != 0) {
????????? Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
???? }
???? try {
????????? msg.target.dispatchMessage(msg);
???? } finally {
???????? if (traceTag != 0) {
???????? Trace.traceEnd(traceTag);
???? }
??? }
??? if (logging != null) {
????????? logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
???? }
???? // Make sure that during the course of dispatching the
??? // identity of the thread wasn't corrupted.
??? final long newIdent = Binder.clearCallingIdentity();
??? if (ident != newIdent) {
??????? Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x"
????????????? + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass).getName() + " " + msg.callback + " what=" + msg.what);
???? }
????? msg.recycleUnchecked();
?? }
}
Looper的loop方法的工作過(guò)程也比較好理解分俯,loop方法是一個(gè)死循環(huán)肾筐,唯一跳出循環(huán)的方式是MessageQueue的next方法返回了null。在此循環(huán)中缸剪,會(huì)不停的通過(guò)MessageQueue的next方法去取消息吗铐,然后通過(guò)msg.target.dispatchMessage(msg),msg.target即是Handler對(duì)象杏节,所以msg會(huì)交給對(duì)應(yīng)的Handler處理唬渗。如果MessageQueue中沒(méi)有消息時(shí),next方法會(huì)一直阻塞在那里奋渔,導(dǎo)致loop方法也一直阻塞镊逝。
由于,此處的loop方法運(yùn)行在創(chuàng)建Handler時(shí)綁定的Looper(線程)上嫉鲸,這樣就完成了將代碼邏輯切換到指定的線程中去執(zhí)行了撑蒜。
回過(guò)頭了,再來(lái)詳細(xì)看一下MessageQueue的next方法和Handler的dispatchMessage方法玄渗。
Message next() {
???? ……
???? int pendingIdleHandlerCount = -1; // -1 only during first iteration
???? int nextPollTimeoutMillis = 0;
??? for (;;) {
??????? if (nextPollTimeoutMillis != 0) {
??????? Binder.flushPendingCommands();
???? }
????? nativePollOnce(ptr, nextPollTimeoutMillis);
???? synchronized (this) {
???? // Try to retrieve the next message.? Return if found.
???? final long now = SystemClock.uptimeMillis();
???? Message prevMsg = null;
???? 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());
???? }
??? 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;
???? if (DEBUG) Log.v(TAG, "Returning message: " + msg);
???? msg.markInUse();
???? return msg;
? }
?} else {
?? // No more messages.
??? nextPollTimeoutMillis = -1;
?}
??? // Process the quit message now that all pending messages have been handled.
?? if (mQuitting) {
??? dispose();
??? return null;
? }
?? // If first time idle, then get the number of idlers to run.
?? // Idle handles only run if the queue is empty or if the first message
?? // in the queue (possibly a barrier) is due to be handled in the future.
?? if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {
??????? pendingIdleHandlerCount = mIdleHandlers.size();
??? }
??? if (pendingIdleHandlerCount <= 0) {
???????? // No idle handlers to run.? Loop and wait some more.
???????? mBlocked = true;
??????? continue;
???? }
??? if (mPendingIdleHandlers == null) {
??????? mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
??? }
???? mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
? }
?? // Run the idle handlers.
??? // We only ever reach this code block during the first iteration.
???? for (int i = 0; i < pendingIdleHandlerCount; i++) {
?????????? final IdleHandler idler = mPendingIdleHandlers[i];
?????????? mPendingIdleHandlers[i] = null; // release the reference to the handler
????????? boolean keep = false;
????????? try {
????????????? keep = idler.queueIdle();
???????? } catch (Throwable t) {
??????????? Log.wtf(TAG, "IdleHandler threw exception", t);
???????? }
??????? if (!keep) {
????????? synchronized (this) {
????????????? mIdleHandlers.remove(idler);
??????????}
??????? }
??? }
???? // Reset the idle handler count to 0 so we do not run them again.
????? pendingIdleHandlerCount = 0;
????? // While calling an idle handler, a new message could have been delivered
????? // so go back and look again for a pending message without waiting.
?????? nextPollTimeoutMillis = 0;
????? }
}
可以看到座菠,next方法是一個(gè)無(wú)限循環(huán)方法,如果沒(méi)有消息捻爷,那么next會(huì)一直阻塞辈灼;如果有新的消息就會(huì)跳出循環(huán),從單鏈表中刪除這條消息也榄,并返回它∷局荆可以看到甜紫,阻塞是通過(guò)native代碼實(shí)現(xiàn)的,next方法里調(diào)用nativePollOnce實(shí)現(xiàn)阻塞骂远,具體的也分為兩種情況囚霸,有消息,但是當(dāng)前消息還沒(méi)到處理的時(shí)間激才,此時(shí)會(huì)阻塞固定的時(shí)間拓型;還有一種情況是,消息隊(duì)列已經(jīng)沒(méi)有消息瘸恼,此時(shí)會(huì)阻塞無(wú)限長(zhǎng)的時(shí)間劣挫,直到外部來(lái)激活它(enqueueMessage方法中的nativeWake方法),具體的細(xì)節(jié)可以參看大神羅升陽(yáng)的博客Android應(yīng)用程序消息處理機(jī)制(Looper东帅、Handler)分析压固。
此外,當(dāng)當(dāng)前沒(méi)有消息需要處理靠闭,在進(jìn)入阻塞前帐我,會(huì)處理注冊(cè)的IdleHandler接口坎炼,利用此接口也可以實(shí)現(xiàn)很多有價(jià)值的功能,具體可以參看Bugly公眾號(hào)的一篇文章你知道Android的MessageQueue.IdleHandler嗎拦键。
接下來(lái)再來(lái)看一下Handler的dispatchMessage方法:
public void dispatchMessage(Message msg) {
??? if (msg.callback != null) {
???????? handleCallback(msg);
??? } else {
???????? if (mCallback != null) {
???????? if (mCallback.handleMessage(msg)) {
???????????? return;
???????? }
????? }
?????? handleMessage(msg);
???? }
}
這里就不用做過(guò)多的解釋了谣光,使用過(guò)Handler的同學(xué)都能明白,其中的handleMessage(msg)語(yǔ)句調(diào)用的就是我們重寫(xiě)的handleMessage方法芬为。此處有一個(gè)需要講解的點(diǎn)就是這里的mCallback有什么用處抢肛,Handler中的注釋其實(shí)就說(shuō)的很明白,此Callback接口的一個(gè)好處就是避免在實(shí)例化Handler時(shí)不得不實(shí)現(xiàn)其子類(lèi)(也就是重寫(xiě)handleMessage方法)碳柱,也可以通過(guò)設(shè)置callback而不去實(shí)現(xiàn)子類(lèi)捡絮。
Handler的內(nèi)部機(jī)制基本就是這樣,其中有一個(gè)點(diǎn)說(shuō)的不是很透徹莲镣,就是Looper.loop()中的final Looper me = myLooper()福稳,是如何拿到當(dāng)前線程的Looper的,簡(jiǎn)單的說(shuō)瑞侮,就是使用了Java中的ThreadLocal類(lèi)的特性的圆,它是一個(gè)線程內(nèi)部的數(shù)據(jù)存儲(chǔ)類(lèi),線程只能獲取該線程存儲(chǔ)的數(shù)據(jù)半火,而不會(huì)獲取到其他線程存儲(chǔ)的數(shù)據(jù)越妈,后面準(zhǔn)備單獨(dú)寫(xiě)一篇文章來(lái)學(xué)習(xí)這個(gè)類(lèi),此處不再詳細(xì)的說(shuō)明钮糖,有興趣的同學(xué)可以自行百度梅掠。
總結(jié)一下,Handler的內(nèi)部機(jī)制由Handler店归、Looper阎抒、MessageQueue、Message共同實(shí)現(xiàn)了線程切換和延時(shí)執(zhí)行的功能消痛。下面的類(lèi)圖列出了相互的關(guān)系且叁,各個(gè)類(lèi)只列出了關(guān)鍵的幾個(gè)public方法,切換線程的核心是目標(biāo)線程中的Looper.loop方法不停的獲取秩伞、處理消息隊(duì)列的消息來(lái)實(shí)現(xiàn)的逞带;延時(shí)執(zhí)行時(shí)通過(guò)native代碼的阻塞來(lái)間接實(shí)現(xiàn)的。
其具體的流程可以簡(jiǎn)單的表示為下圖:
Handler使用過(guò)程中的內(nèi)存泄漏問(wèn)題
Java使用有向圖機(jī)制纱新,通過(guò)GC自動(dòng)檢查內(nèi)存中的對(duì)象(什么時(shí)候檢查由虛擬機(jī)決定)展氓,如果GC發(fā)現(xiàn)一個(gè)或一組對(duì)象為不可到達(dá)狀態(tài),則將該對(duì)象從內(nèi)存中回收怒炸。也就是說(shuō)带饱,一個(gè)對(duì)象不被任何引用所指向,則該對(duì)象會(huì)在被GC發(fā)現(xiàn)的時(shí)候被回收;另外勺疼,如果一組對(duì)象中只包含互相的引用教寂,而沒(méi)有來(lái)自它們外部的引用(例如有兩個(gè)對(duì)象A和B互相持有引用,但沒(méi)有任何外部對(duì)象持有指向A或B的引用)执庐,這仍然屬于不可到達(dá)酪耕,同樣會(huì)被GC回收。
Android中使用Handler造成內(nèi)存泄露的原因:
private Handler handler = new Handler() {? ? ?
??? public void handleMessage(android.os.Message msg) {???????????
??? if (msg.what == 1)? {? ? ? ? ? ? ? ?
??????? doSomeThing();? ? ? ? ? ?
???? }? ? ? ?
} };
上面是一段簡(jiǎn)單的Handler的使用轨淌。當(dāng)使用內(nèi)部類(lèi)(包括匿名類(lèi))來(lái)創(chuàng)建Handler的時(shí)候迂烁,Handler對(duì)象會(huì)隱式地持有一個(gè)外部類(lèi)對(duì)象(通常是一個(gè)Activity)的引用(不然你怎么可能通過(guò)Handler來(lái)操作Activity中的View?)递鹉。而Handler通常會(huì)伴隨著一個(gè)耗時(shí)的后臺(tái)線程(例如從網(wǎng)絡(luò)拉取圖片)一起出現(xiàn)盟步,這個(gè)后臺(tái)線程在任務(wù)執(zhí)行完畢(例如圖片下載完畢)之后,通過(guò)消息機(jī)制通知Handler躏结,然后Handler把圖片更新到界面却盘。然而,如果用戶在網(wǎng)絡(luò)請(qǐng)求過(guò)程中關(guān)閉了Activity媳拴,正常情況下黄橘,Activity不再被使用,它就有可能在GC檢查時(shí)被回收掉屈溉,但由于這時(shí)線程尚未執(zhí)行完塞关,而該線程持有Handler的引用(不然它怎么發(fā)消息給Handler?)子巾,這個(gè)Handler又持有Activity的引用帆赢,就導(dǎo)致該Activity無(wú)法被回收(即內(nèi)存泄露),直到網(wǎng)絡(luò)請(qǐng)求結(jié)束(例如圖片下載完畢)砰左。另外匿醒,如果你執(zhí)行了Handler的postDelayed()方法,該方法會(huì)將你的Handler裝入一個(gè)Message缠导,并把這條Message推到MessageQueue中,那么在你設(shè)定的delay到達(dá)之前溉痢,會(huì)有一條MessageQueue -> Message -> Handler -> Activity的鏈僻造,導(dǎo)致你的Activity被持有引用而無(wú)法被回收。
使用Handler導(dǎo)致內(nèi)存泄露的解決方法可以有以下兩個(gè)方法:
方法一:通過(guò)程序邏輯來(lái)進(jìn)行保護(hù)孩饼。
1.在關(guān)閉Activity的時(shí)候停掉你的后臺(tái)線程髓削。線程停掉了,就相當(dāng)于切斷了Handler和外部連接的線镀娶,Activity自然會(huì)在合適的時(shí)候被回收立膛。
2.如果你的Handler是被delay的Message持有了引用,那么使用相應(yīng)的Handler的removeCallbacks()方法,把消息對(duì)象從消息隊(duì)列移除就行了宝泵。
方法二:將Handler聲明為靜態(tài)類(lèi)好啰,內(nèi)部使用弱引用持有外部類(lèi)對(duì)象。
這是由于在Java 中儿奶,非靜態(tài)的內(nèi)部類(lèi)和匿名內(nèi)部類(lèi)都會(huì)隱式地持有其外部類(lèi)的引用框往,靜態(tài)的內(nèi)部類(lèi)不會(huì)持有外部類(lèi)的引用。
靜態(tài)類(lèi)不持有外部類(lèi)的對(duì)象闯捎,所以你的Activity可以隨意被回收椰弊。由于Handler不再持有外部類(lèi)對(duì)象的引用,導(dǎo)致程序不允許你在Handler中操作Activity中的對(duì)象了瓤鼻。所以你需要在Handler中增加一個(gè)對(duì)Activity的弱引用(WeakReference)秉版。
這里推薦使用第二種方法,這里引出下面Handler使用的最佳實(shí)踐茬祷。
最佳實(shí)踐
將Handler聲明為靜態(tài)類(lèi)后清焕,實(shí)現(xiàn)如下:
private static class MyHandler extends Handler {
????????? private final WeakReferencemActivity;
??????????? public MyHandler(HandlerActivity activity) {
????????????? mActivity = new WeakReference(activity);
????????? }
??????????? @Override? ? ? ? ?
?????????? public void handleMessage(Message msg) {
??????????????? if (mActivity.get() == null) {?
??????????????? return;?
??????????? }
????????????if (msg.what == 1)? {
??????????????? mActivity .get().doSomeThing();?
????????? }?
??????? }
????? }
除此之外,當(dāng)Activity finish后 handler對(duì)象還是在Message中排隊(duì)牲迫。 還是會(huì)處理消息耐朴,這些處理有必要?? 正常Activitiy finish后盹憎,已經(jīng)沒(méi)有必要對(duì)消息處理筛峭,那需要怎么做呢?? 解決方案也很簡(jiǎn)單陪每,在Activity onStop或者onDestroy的時(shí)候影晓,取消掉該Handler對(duì)象的Message和Runnable。
如果在一個(gè)大型的工程的檩禾,我們也可以實(shí)現(xiàn)一個(gè)基類(lèi)挂签,來(lái)規(guī)范Handler的使用,例如谷歌內(nèi)置的LatinIME中實(shí)現(xiàn)了這樣一個(gè)基類(lèi)可以借鑒盼产,所有使用Handler的地方饵婆,都繼承此基類(lèi)來(lái)實(shí)現(xiàn)具體子類(lèi)。
public class LeakGuardHandlerWrapperextends Handler {? ?
???? private final WeakReferencemOwnerInstanceRef;
??? public LeakGuardHandlerWrapper(final T ownerInstance) {
??? this(ownerInstance, Looper.myLooper());
??? }
??? public LeakGuardHandlerWrapper(final T ownerInstance, final Looper looper) {
????????super(looper);
?????? ?if (ownerInstance == null) {
???????????? throw new NullPointerException("ownerInstance is null");
??????? }
???????? mOwnerInstanceRef = new WeakReference<>(ownerInstance);
???? }
??? public T getOwnerInstance() {
???????? return mOwnerInstanceRef.get();
??? }
}