Handler 使得Android開發(fā)難度大大降低,幾乎看不到多線程死鎖的問(wèn)題。
Q:談?wù)勏C(jī)制Hander?作用?有哪些要素?流程是怎樣的?
1、Android的消息機(jī)制主要是指Handler的運(yùn)行機(jī)制,Handler的運(yùn)行依賴于MessageQueue和Looper,當(dāng)然,既然是消息機(jī)制,通常也需要用到Message。Handler、Looper、MessageQueue和Message的工作原理就像是生產(chǎn)線,Looper是發(fā)動(dòng)機(jī),MessageQueue就是傳送帶,Handler是工人,Message就是待處理的產(chǎn)品。
Handler工作流程
Handler相當(dāng)于一個(gè)傳送帶,handler.sendXXX,handler.postXXX都是將消息發(fā)送出去,通過(guò)傳送帶,調(diào)用handler.dispatch()將消息獲取到并處理。handler通過(guò)send將一個(gè)個(gè)的Message放到傳送帶里面來(lái),拼湊成一個(gè)鏈表結(jié)構(gòu),再由Looper函數(shù),調(diào)用Looper.loop方法,相當(dāng)于給這個(gè)傳送帶附上的一個(gè)開關(guān),打開以后為傳送帶供能,傳送帶就不停的滾動(dòng),帶動(dòng)MessageQueue滾動(dòng),消息Message 就一直向前走,當(dāng)Message到達(dá)某個(gè)時(shí)間節(jié)點(diǎn)的時(shí)候,消息將會(huì)發(fā)送出去(handler.sendXXX)交由Hanler去處理。
handler.send() ----->>>>>MessageQueue.enqueueMessage() 消息入隊(duì)列
Looper.loop()----->>>>>MessageQueue.next()--->handler.dispatch() 消息出隊(duì)列
Thread ---->Looper.loop()由線程本身調(diào)用
image.png
2、Handler主要是為了解決在子線程中無(wú)法訪問(wèn)UI的矛盾。
如何實(shí)現(xiàn)線程數(shù)據(jù)的隔離:線程的上下文獨(dú)一無(wú)二?
Thread---->static final ThreadLocal --->Looper 通過(guò)prepare保障了ThreadLocal 和Looper 唯一綁定
Looper唯一----->MessageQueue--->唯一
image.png
Q:為什么系統(tǒng)不建議在子線程訪問(wèn)UI?
因?yàn)锳ndroid的UI是線程不安全的,UI負(fù)責(zé)與用戶的交互,如果多線程中并發(fā)訪問(wèn)UI會(huì)導(dǎo)致UI處于不可控的狀態(tài)。其次也不能選擇使用加鎖處理,首先因?yàn)榧渔i會(huì)阻塞某些線程的執(zhí)行,降低UI訪問(wèn)的效率,其次加鎖會(huì)讓UI訪問(wèn)的邏輯變得復(fù)雜。
UI線程直接與用戶交互,如果在UI線程處理耗時(shí)操作,用戶將無(wú)法繼續(xù)執(zhí)行其他UI操作,用戶體驗(yàn)很差。其次Android規(guī)定,如果任意一個(gè)Acitivity沒有響應(yīng)5秒鐘以上就會(huì)彈出ANR窗口。因此我們可以使用Handler將耗時(shí)操作放到子線程中去執(zhí)行以避免上述情況。
Q:一個(gè)Thread可以有幾個(gè)Looper?幾個(gè)Handler?
一個(gè)Thread只有一個(gè)Looper,一個(gè)looper,多個(gè)Handler。
https://www.
Q:如何將一個(gè)Thread線程變成Looper線程?Looper線程有哪些特點(diǎn)?
Looper和MessageQueue共同協(xié)作來(lái)讓整個(gè)消息隊(duì)列動(dòng)起來(lái),不斷的取出新消息
Q:可以在子線程直接new一個(gè)Handler嗎?那該怎么做?
不能直接new,會(huì)報(bào)錯(cuò),當(dāng)我們?cè)谥骶€程中創(chuàng)建Handler對(duì)象的時(shí)候沒有問(wèn)題,是因?yàn)橹骶€程會(huì)自動(dòng)調(diào)用Looper.prepare()方法去
給當(dāng)前主線程創(chuàng)建并設(shè)置一個(gè)Looper對(duì)象,隨意在Handler構(gòu)造函數(shù)中從當(dāng)前線程的對(duì)象身上拿到這個(gè)Looper。
但是子線程中并不會(huì)自動(dòng)調(diào)用這個(gè)方法,所以要想在子線程中創(chuàng)建Handler對(duì)象就必須在創(chuàng)建之前手動(dòng)調(diào)用Looper.prepare()方
法,否則就會(huì)報(bào)錯(cuò)。
Q:Message可以如何創(chuàng)建?哪種效果更好,為什么?
由于我們是使用Message來(lái)進(jìn)行消息傳遞,所以如果每次都new 一個(gè)Message就很可能會(huì)產(chǎn)生大量用了一次就不再用的Message對(duì)象,消耗有限的內(nèi)存資源。針對(duì)這種情況,Handler的obtainMessage()方法內(nèi)部采用了享元模式。我們應(yīng)該優(yōu)先使用Handler的obtainMessage()方法構(gòu)建Message對(duì)象。
Q:這里的ThreadLocal有什么作用?
它的存在讓Thread、Handler、MessageQueue綁定了起來(lái),于是讓handler可以做到不管在哪個(gè)線程發(fā)消息,最終消息都會(huì)傳送到原來(lái)的Thread里。ThreadLocalMap也是功不可沒。
Q:主線程中Looper的輪詢死循環(huán)為何沒有阻塞主線程?
ActivityThread的main方法主要就是做消息循環(huán),一旦退出消息循環(huán),那么你的應(yīng)用也就退出了。因?yàn)锳ndroid 的是由事件驅(qū)動(dòng)的,looper.loop() 不斷地接收事件、處理事件,每一個(gè)點(diǎn)擊觸摸或者說(shuō)Activity的生命周期都是運(yùn)行在 Looper.loop() 的控制之下,如果它停止了,應(yīng)用也就停止了。只能是某一個(gè)消息或者說(shuō)對(duì)消息的處理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它。
也就說(shuō)我們的代碼其實(shí)就是在這個(gè)循環(huán)里面去執(zhí)行的,當(dāng)然不會(huì)阻塞了。
如果某個(gè)消息處理時(shí)間過(guò)長(zhǎng),比如你在onCreate(),onResume()里面處理耗時(shí)操作,那么下一次的消息比如用戶的點(diǎn)擊事件不能處理了,整個(gè)循環(huán)就會(huì)產(chǎn)生卡頓,時(shí)間一長(zhǎng)就成了ANR。
而且主線程Looper從消息隊(duì)列讀取消息,當(dāng)讀完所有消息時(shí),主線程阻塞。子線程往消息隊(duì)列發(fā)送消息,并且往管道文件寫數(shù)據(jù),主線程即被喚醒,從管道文件讀取數(shù)據(jù),主線程被喚醒只是為了讀取消息,當(dāng)消息讀取完畢,再次睡眠。因此loop的循環(huán)并不會(huì)對(duì)CPU性能有過(guò)多的消耗。
總結(jié):Looer.loop()方法可能會(huì)引起主線程的阻塞,但只要它的消息循環(huán)沒有被阻塞,能一直處理事件就不會(huì)產(chǎn)生ANR異常。
Q:使用Hanlder的postDealy()后消息隊(duì)列會(huì)發(fā)生什么變化?
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;
}
...
}
可以看到,在這個(gè)方法內(nèi),如果頭部的這個(gè)Message是有延遲而且延遲時(shí)間沒到的(now < msg.when),會(huì)計(jì)算一下時(shí)間(保存為變量nextPollTimeoutMillis),然后在循環(huán)開始的時(shí)候判斷如果這個(gè)Message有延遲,就調(diào)用nativePollOnce(ptr, nextPollTimeoutMillis)進(jìn)行阻塞。nativePollOnce()的作用類似與object.wait(),只不過(guò)是使用了Native的方法對(duì)這個(gè)線程精確時(shí)間的喚醒。
Q:Handler的內(nèi)存泄露問(wèn)題及其處理方法
成因:
Handler的內(nèi)存泄露問(wèn)題大致是這樣形成的:就是主線程中創(chuàng)建的Handler會(huì)和主線程Looper的消息隊(duì)列MessageQueue相關(guān)聯(lián),于是MessageQueue中的每個(gè)Message都會(huì)持有一個(gè)Handler的引用。而由于非靜態(tài)內(nèi)部類和匿名類都會(huì)隱式的持有它們所屬外部類的引用,所以就導(dǎo)致了Message持有Handler引用,Handler持有其外部類的引用的引用鏈。在Message被處理前,這條引用鏈會(huì)阻止垃圾回收器的回收,于是發(fā)生內(nèi)存泄露。
驗(yàn)證:可以使用 handler.postDelayed()方法進(jìn)行驗(yàn)證。
解決:
解決方法:1. 使用內(nèi)部靜態(tài)類構(gòu)造Handlr,因?yàn)閮?nèi)部靜態(tài)類不會(huì)持有外部類的引用。但是這樣的話就無(wú)法操控外部Activity的對(duì)象,于是還需要增加一個(gè)隊(duì)Activity的弱引用。2. 在Activity退出的時(shí)候調(diào)用Looper的quit和quitSafely方法,以及使用對(duì)應(yīng)的handler.removeCallback方法,這些方法會(huì)最后會(huì)執(zhí)行 msg.target = null 操作,讓msg不再持有handler引用。

