文章獨(dú)家授權(quán)公眾號(hào):碼個(gè)蛋
更多分享:http://www.cherylgood.cn
- 談到Android開發(fā)钢颂,就離不開線程操作,而面試中也會(huì)常常問到有關(guān)異步線程募谎、多線程扶关、Handler等問題,作為面試中中獎(jiǎng)率如此之高的一個(gè)問題数冬,我們今天不妨來瞅瞅這handler長(zhǎng)啥樣节槐!
目前:假設(shè)我們需要在子線程中更新UI,一般有以下幾種方式:
- 1、view.post(Runnable action)
- 2疯淫、activity.runOnUiThread(Runnable action)
- 3地来、AsyncTask
- 4、Handler
而我們今天的主要目標(biāo)就是Handler熙掺,咱來一層一層撕開它的真面目吧!
說了那么多廢話咕宿,我們開始進(jìn)入正題币绩,首先我們看下handler的官方定義:
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允許你通過使用一個(gè)與線程的MessageQueue相關(guān)聯(lián)的Message和Runnable對(duì)象去發(fā)送和處理消息。 每個(gè)處理程序?qū)嵗c單個(gè)線程和該線程的消息隊(duì)列相關(guān)聯(lián)府阀。 當(dāng)您創(chuàng)建一個(gè)新的處理程序時(shí)缆镣,它綁定到正在創(chuàng)建它的線程的線程/消息隊(duì)列 - 從那時(shí)起,它將向消息隊(duì)列傳遞消息和可運(yùn)行文件试浙,并在消息發(fā)出時(shí)執(zhí)行它們 隊(duì)列董瞻。
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有兩個(gè)主要用途:(1)在可預(yù)見的時(shí)間內(nèi)去調(diào)度消息和作為一些點(diǎn)的可運(yùn)行程序(2)將不同于自己的線程執(zhí)行的操作排入隊(duì)列中。
Scheduling messages is accomplished with the post(Runnable), postAtTime(Runnable, long), postDelayed(Runnable, long), sendEmptyMessage(int), sendMessage(Message), sendMessageAtTime(Message, long), and sendMessageDelayed(Message, long) methods. The post versions allow you to enqueue Runnable objects to be called by the message queue when they are received; the sendMessage versions allow you to enqueue a Message object containing a bundle of data that will be processed by the Handler's handleMessage(Message) method (requiring that you implement a subclass of Handler).
消息的調(diào)度是通過post(Runnable)田巴,postAtTime(Runnable钠糊,long),postDelayed(Runnable壹哺,long)抄伍,sendEmptyMessage(int),sendMessage(Message)管宵,sendMessageAtTime(Message截珍,long)和sendMessageDelayed(Message,long)來完成的 箩朴。 后臺(tái)版本允許你將接收到的消息隊(duì)列調(diào)用的Runnable對(duì)象排入隊(duì)列; sendMessage版本允許你將包含將由處理程序的handleMessage(Message)方法處理的數(shù)據(jù)包(要求您實(shí)現(xiàn)Handler的子類)的Message對(duì)象排入隊(duì)列岗喉。
When posting or sending to a Handler, you can either allow the item to be processed as soon as the message queue is ready to do so, or specify a delay before it gets processed or absolute time for it to be processed. The latter two allow you to implement timeouts, ticks, and other timing-based behavior.
當(dāng)發(fā)布或發(fā)送到Handler時(shí),你可以在消息隊(duì)列準(zhǔn)備就緒后立即處理該項(xiàng)目或者指定一個(gè)延遲時(shí)間去處理該消息隊(duì)列炸庞,或者指定一個(gè)具體時(shí)間處理該消息钱床。 后兩者允許您實(shí)現(xiàn)超時(shí),定時(shí)和其他基于時(shí)間的行為燕雁。
When a process is created for your application, its main thread is dedicated to running a message queue that takes care of managing the top-level application objects (activities, broadcast receivers, etc) and any windows they create. You can create your own threads, and communicate back with the main application thread through a Handler. This is done by calling the same post or sendMessage methods as before, but from your new thread. The given Runnable or Message will then be scheduled in the Handler's message queue and processed when appropriate.
當(dāng)為你的應(yīng)用創(chuàng)建一個(gè)進(jìn)程時(shí)诞丽,其主線程專用于運(yùn)行一個(gè)消息隊(duì)列,該消息隊(duì)列負(fù)責(zé)管理頂級(jí)應(yīng)用程序?qū)ο螅╝ctivitys拐格, broadcast receivers 等)及其創(chuàng)建的任何窗口僧免。 你可以創(chuàng)建你自己的線程并通過Handler與主應(yīng)用程序線程進(jìn)行通信。 這可以通過從你的新線程中調(diào)用同樣的post或sendMessage方法來實(shí)現(xiàn)捏浊。 給定的Runnable或Message將在Handler的消息隊(duì)列中進(jìn)行調(diào)度懂衩,并在適當(dāng)時(shí)進(jìn)行處理。
在查看Handler源碼之前,我們先了解幾個(gè)類:
Handler 浊洞、Looper牵敷、MessageQueue、Message法希、ThreadLocation
Handler我們就不在介紹該類枷餐,上面的官方文檔已給出了詳細(xì)的介紹,我們來看下其余幾個(gè):
- 1苫亦、ThreadLocal:每個(gè)使用該變量的線程提供獨(dú)立的變量副本毛肋,每一個(gè)線程都可以獨(dú)立地改變自己的副本,而不會(huì)影響其它線程所對(duì)應(yīng)的副本屋剑。ThreadLocal內(nèi)部是通過map進(jìn)行實(shí)現(xiàn)的润匙;
- 2、Looper:可以理解為循環(huán)器唉匾,就是扶著管理一個(gè)消息循環(huán)隊(duì)列(MessageQueue)的;
- 3孕讳、MessageQueue:消息隊(duì)列,用來存放handler發(fā)布的消息
- 4巍膘、Message:消息體厂财,封裝了我們傳輸消息所需的數(shù)據(jù)結(jié)構(gòu)。
-
ok典徘,那么我們從哪里開始看起呢蟀苛,好吧, 從創(chuàng)建一個(gè)Handler實(shí)例為入口逮诲,首先我們看handler的構(gòu)造方法:
public Handler() { this(null, false); } public Handler(Callback callback) { this(callback, false); } public Handler(Looper looper) { this(looper, null, false); } public Handler(Looper looper, Callback callback) { this(looper, callback, false); } public Handler(boolean async) { this(null, async); } public Handler(Callback callback, boolean async) { if (FIND_POTENTIAL_LEAKS) { final Class<? extends Handler> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, "The following Handler class should be static or leaks might occur: " + klass.getCanonicalName()); } } mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; } public Handler(Looper looper, Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; }
可以看到帜平,有多個(gè)構(gòu)造方法,但是最終都會(huì)調(diào)用到最后一個(gè)梅鹦。我們看倒數(shù)第二個(gè)裆甩,有這么一句: mLooper = Looper.myLooper();這里我們看到了個(gè)looper,可以看到齐唆,在handler里面會(huì)有一個(gè)mlooper對(duì)象與之關(guān)聯(lián)嗤栓,我們先不看mlooper是怎么來的,我們先把下面的看完箍邮;繼續(xù)看下面一句: mQueue = mLooper.mQueue;我們的handler里面也有一個(gè)隊(duì)列的對(duì)象茉帅,實(shí)際上mQueue就是MessageQueue,后面我們會(huì)講解到锭弊。好的堪澎,繼續(xù)往下看, mCallback = callback;一般情況下mCallback是null味滞,我們通常new 一個(gè)Handler是不是調(diào)用的無參構(gòu)造方法樱蛤?callback的作用后面也會(huì)講解到钮呀,好的最后一句: mAsynchronous = async;表示我們的執(zhí)行過程是異步的還是同步的,一般情況下昨凡,默認(rèn)是異步的爽醋。
小結(jié): Handler會(huì)存有Looper對(duì)象以及消息隊(duì)列mQueue,通過關(guān)聯(lián)looper與mQueue便脊,可以想象蚂四,handler要把message插入消息隊(duì)列中,最直接的方式當(dāng)然是拿到消息隊(duì)列的實(shí)例哪痰,實(shí)現(xiàn)消息的發(fā)送证杭;
-
看了Handler的構(gòu)造,接下來我們看下Looper.mLooper:
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
-可以看到,Looper.myLooper內(nèi)部是調(diào)用了sThreadlocal.get();這個(gè)sThreadLocal其實(shí)就是我們之前說的ThreadLocal類的實(shí)例镇饺,他負(fù)責(zé)存儲(chǔ)當(dāng)先線程的Looper實(shí)例乎莉;是不是真的呢?我們看下sThreadLocal在哪里賦值的奸笤,很好惋啃,我們找到了一個(gè)prepare方法,看名字是準(zhǔn)備的意思监右,也就是為我們準(zhǔn)備我們需要的looper對(duì)象边灭,我們繼續(xù)看:public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); }
-
首先我們可以看到,會(huì)先判斷sThreadLocal.get() != null健盒,說明Looper.prepare()只能被調(diào)用一次哦绒瘦,不然就會(huì)拋出異常,這樣做是為了保證一個(gè)線程只有一個(gè)looper存在扣癣,然后我們的可以看到里面通過new Looper(quitAllowed)獲得當(dāng)先線程的looper惰帽,我們繼續(xù)看Looper的構(gòu)造方法:
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
在looper的構(gòu)造方法里,主要做了兩件事:1父虑、創(chuàng)建一個(gè)looper管理的消息隊(duì)列 messageQueue该酗;2、獲得當(dāng)前的線程士嚎;
小結(jié):Looper里面會(huì)存儲(chǔ)當(dāng)前的線程呜魄,以及所管理的消息隊(duì)列mQueue,一個(gè)Looper只會(huì)管理一個(gè)消息隊(duì)列MessageQueue莱衩;
從上面的代碼中我們可以知道爵嗅,在new 一個(gè)handler的同時(shí),我們就獲得了一個(gè)handler實(shí)例膳殷、一個(gè)當(dāng)前線程的looper操骡、一個(gè)looper管理的messagequeue九火,好像擁有了這三個(gè)對(duì)象,我們就可以發(fā)送消息了哦册招。
-
大家都知道looper從創(chuàng)建之后岔激,就會(huì)開始循環(huán),在looper類的頂部是掰,官方給出了一段代碼:
class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { // process incoming messages here } }; Looper.loop(); }
當(dāng)我們使用handler發(fā)消息時(shí)虑鼎,步驟是:
- 1、 調(diào)用 Looper.prepare(); 初始化所需的looper以及messageQueue
- 2键痛、 實(shí)例化一個(gè)handler對(duì)象炫彩,我們可以在handleMessage獲得message做一些操作,此時(shí)handleMessage方法是在當(dāng)前的Looper中執(zhí)行的絮短,也就是說江兢,如果當(dāng)前的looper是UI Looper,那么你可以更新UI丁频,如果當(dāng)前l(fā)ooper不是UI Looper杉允,那么你更新UI肯定會(huì)報(bào)錯(cuò),你可能會(huì)說席里,我用handler時(shí)叔磷,好像都不用調(diào)用Looper.prepare();,我怎么知道我當(dāng)前的looper是UI的還是不是呢奖磁,其實(shí)系統(tǒng)一般默認(rèn)都幫我們獲取了UI 的Looper改基,后面我們會(huì)講解到;
- 3咖为、調(diào)用 Looper.loop();讓Looper跑起來吧秕狰!
-
Looper.prepare();我們前面已經(jīng)分析過了,主要是實(shí)例化一個(gè)messageQueue案疲,而且只能調(diào)用一次封恰;那么我們重點(diǎn)就轉(zhuǎn)移懂到 Looper.loop();看源碼:
/** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. */ 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.isTagEnabled(traceTag)) { 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(); } }
1、調(diào)用final Looper me = myLooper();獲得一個(gè)looper褐啡,myLooper方法我們前面分析過诺舔,返回的是sThreadLocal中存儲(chǔ)的Looper實(shí)例,當(dāng)me==null拋出異常备畦;所以低飒,在looper執(zhí)行l(wèi)oop跑起來之前,我們要記得調(diào)用prepare()哦懂盐。當(dāng)獲得當(dāng)前的looper后褥赊,調(diào)用 final MessageQueue queue = me.mQueue; 獲取looper管理的MessageQueue;然后我們可以看到一個(gè)很有意思的for語句: for (;;) {...} 這就是循環(huán)的開始了莉恼,此時(shí)我在想拌喉,我的天速那,這不是個(gè)無限死循話么?怎么可能呢尿背?當(dāng)然有退出的條件端仰,不然不就傻逼了么!
2田藐、我們可以看到:他會(huì)從looper的queue中獲取message荔烧,當(dāng)message==null,循環(huán)停止汽久!
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}3鹤竭、循環(huán)起來了,咱的looper也沒閑著景醇,他一直知道它的工作是什么臀稚,我們可以看到:msg.target.dispatchMessage(msg);通過調(diào)用msg對(duì)象里的target對(duì)象的dispatchMessage(msg)方法把消息處理了。其實(shí)msg對(duì)象里的target對(duì)象就是我們new出來的handler三痰,我們后面會(huì)講到烁涌。
小結(jié):
looper主要做了如下工作:
- 1、將自己與當(dāng)前線程關(guān)聯(lián)在一起酒觅,通過ThreadLocal存儲(chǔ)當(dāng)前線程的looper,確保當(dāng)前線程只有一個(gè)looper實(shí)例微峰;
- 2舷丹、創(chuàng)建一個(gè)MessageQueue與當(dāng)前l(fā)ooper綁定,通過prepare方法控制looper只能有一個(gè)messageQueue實(shí)例蜓肆;
- 3颜凯、調(diào)用loop()方法,不斷從MessageQueue中去取消息仗扬,通過調(diào)用msg.target.dispatchMessage(msg)處理;
-
分析完了looper症概、接下來當(dāng)然是hanlder發(fā)送消息了,我們又回到了handler中早芭,我們通過handler發(fā)消息彼城,自然少不了我們得sendMessag方法,那么我們就從它入手吧:
public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0); } public final boolean sendEmptyMessage(int what) { return sendEmptyMessageDelayed(what, 0); } public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { Message msg = Message.obtain(); msg.what = what; return sendMessageDelayed(msg, delayMillis); } public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) { Message msg = Message.obtain(); msg.what = what; return sendMessageAtTime(msg, uptimeMillis); } public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } 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); }
- 可以看到我們的sendMessage有多種方法退个,但最終都會(huì)調(diào)用enqueueMessage方法募壕,我們看enqueueMessage方法源碼:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
} - 可以看到里面會(huì)講當(dāng)前的this賦值給msg.target,this
當(dāng)前就是我們當(dāng)前的handler了语盈,這也就是之前在分析looper時(shí)說的舱馅,通過調(diào)用msg.target. dispatchMessage(msg)方法處理消息;后面后調(diào)用queue.enqueueMessage(msg, uptimeMillis);把消息放入當(dāng)前的looper的MessageQueue隊(duì)列中去處理刀荒,消息的發(fā)送流程就分析完了代嗤,發(fā)送了棘钞,接下來就是處理消息了!
我們用handler時(shí)干毅,都是在handleMessage方法中處理消息的宜猜,那么我們就從handleMessage方法入手:
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}-
可以看到handleMessage是一個(gè)空的方法,我們看handleMessage在哪被調(diào)用的呢溶锭?
/** * Handle system messages here. */ public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
可以看到handleMessage在dispatchMessage中被調(diào)用了宝恶,奇怪,怎么有兩個(gè)handleMessage方法呢趴捅?大家不要弄混了哦垫毙,我們handler的handleMessage方法返回值時(shí)void,所以mCallback.handleMessage肯定不是我們handler的了拱绑;
第一個(gè)縣判斷msg.callback!=null 調(diào)用 handleCallback(msg);
然后我們追進(jìn)去看:
private static void handleCallback(Message message) {
message.callback.run();
}
看到了run(),是不是想到了Runnable综芥?其實(shí)message中的callback就是Runnable,我們可以從Message的創(chuàng)建函數(shù)中看到:
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}我們繼續(xù)回到dispatchMessage方法:也就是說猎拨,如果我們的給massge設(shè)置了callback膀藐,那么我們的handleMessage方法就不會(huì)被執(zhí)行了,當(dāng)然红省,一般我們的massge.callback都是null的额各。后面就會(huì)繼續(xù)判斷mCallback!=null如果成立則調(diào)用mCallback.handleMessage(msg) mCallback其實(shí)是一個(gè)回調(diào)接口,可以看到吧恃,如果mCallback.handleMessage(msg)返回true虾啦,就不會(huì)執(zhí)行我們的Handler.handleMessage方法,所以我們其實(shí)可以通過給handler添加Callback來實(shí)現(xiàn)一個(gè)message的過濾或者攔截功能痕寓。
我們的Handler.handleMessage經(jīng)過重重阻撓傲醉,最終終于可以執(zhí)行了。
總結(jié):
- 1呻率、在Looper.prepare()中會(huì)通過sThreadLocal保存一個(gè)looper實(shí)例硬毕,控制當(dāng)前線程只能有一個(gè)looper實(shí)例;
- 2礼仗、創(chuàng)建looper實(shí)例時(shí)吐咳,會(huì)創(chuàng)建一個(gè)MessageQueue與looper關(guān)聯(lián);
- 3元践、因?yàn)閘ooper只會(huì)存在一個(gè)實(shí)例挪丢,所以 當(dāng)前線程也會(huì)只存在一個(gè)MessageQueue隊(duì)列;
- 4卢厂、調(diào)用Looper.loop()讓looper跑起來吧乾蓬,然后looper就可以不停的從MessageQueue把消息拿出來,然后通過調(diào)用msg.target.dispatchMessage(msg)處理消息慎恒,也是讓消息最終進(jìn)入我們的Handler.handleMessage方法任内,被我們給處理了撵渡;所以我們?cè)趯?shí)例化handler時(shí)需要重寫handleMessage方法;
- 5死嗦、實(shí)例化Handler時(shí)趋距,handler中會(huì)獲得當(dāng)前線程的looper以及l(fā)ooper的messageQueue;
- 6越除、在調(diào)用sendMessage發(fā)送消息時(shí)节腐,最終會(huì)調(diào)用enqueueMessage方法,在enqueueMessage方法里會(huì)將msg.target=handler摘盆,講handler關(guān)聯(lián)到msg中翼雀,這樣looper在取出messageQueue中的消息時(shí),才知道該消息是要發(fā)給那個(gè)handler處理的孩擂,將handler與msg關(guān)聯(lián)后狼渊,就將msg加入隊(duì)列中去了,等待looper處理类垦。
-
來個(gè)圖解吧狈邑,畫的不是很好
Looper.png 使用Handler注意事項(xiàng):
- 1、創(chuàng)建massage對(duì)象時(shí),推薦使用obtain()方法獲取蚤认,因?yàn)镸essage內(nèi)部會(huì)維護(hù)一個(gè)Message池用于Message的復(fù)用米苹,這樣就可以避免 重新new message而沖內(nèi)心分配內(nèi)存,減少new 對(duì)象產(chǎn)生的資源的消耗砰琢。
- 2驱入、handler 的handleMessage方法內(nèi)部如果有調(diào)用外部activity或者fragment的對(duì)象,一定要用弱飲用氯析,handler最好定義成static的,這樣可以避免內(nèi)存泄漏莺褒;為什么呢掩缓?因?yàn)橐坏玥andler發(fā)送了消息。而handler內(nèi)部有對(duì)外部變量的引用遵岩,此時(shí)handler已經(jīng)進(jìn)入了looper的messageQueue里面你辣。此時(shí)activity或者fragment退出了可視區(qū)域,但是handler內(nèi)部持有其引用且為強(qiáng)引用時(shí)尘执,其就不會(huì)立即銷毀舍哄,產(chǎn)生延遲銷毀的情況。