每日一題: Handler源碼
面試率: ★★★☆☆
面試技巧與建議
handler作為Android的主線程,是其主要的消息機(jī)制,作為的軟件開(kāi)發(fā)人員,掌握并熟知handler底層運(yùn)行原理已經(jīng)成為了Android開(kāi)發(fā)的一種標(biāo)配,面試必問(wèn).
面試建議
handler涉及很廣,我們可以選擇適合自己的深度來(lái)局部掌握該面試題,為什么局部掌握,因?yàn)槿秩チ私夂馁M(fèi)精力太大,而且原理邏輯繁雜,不適合快速吸收.因此下面我給出了兩種方案:
深入者如Looper樟蠕、Handler馏予、Message三者關(guān)系
淺入者如handler性能優(yōu)化,常見(jiàn)方法使用與區(qū)別,handler與子線程等
相關(guān)問(wèn)題.
面試技巧
其實(shí)可以從側(cè)面的角度去分析這個(gè)問(wèn)題,如實(shí)際項(xiàng)目中如何正確的使用handler.使用時(shí)遇到過(guò)什么問(wèn)題,如何解決該問(wèn)題.
面試題
下面是從handler的源碼和實(shí)際開(kāi)發(fā)中提取出的一些面試問(wèn)題,從實(shí)際中出發(fā)探討handler源碼,面試中最恰當(dāng)不過(guò)的事情,莫過(guò)于此.
一個(gè)線程可以有幾個(gè)Lopper實(shí)例,為什么?
一個(gè)線程中只有一個(gè)Looper實(shí)例.
sThreadLocal是一個(gè)ThreadLocal對(duì)象榛鼎,可以在一個(gè)線程中存儲(chǔ)變量园欣∑浚可以看到狮杨,在第5行豺妓,將一個(gè)Looper的實(shí)例放入了ThreadLocal砂代,并且2-4行判斷了sThreadLocal是否為null蹋订,否則拋出異常。這也就說(shuō)明了Looper.prepare()方法不能被調(diào)用兩次刻伊,同時(shí)也保證了一個(gè)線程中只有一個(gè)Looper實(shí)例.
源碼
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(true));
}
繼續(xù)看Lopper的構(gòu)造方法做了什么,那里面的loop()方法呢,知道finalfinal Looper me = myLooper()
的作用嗎?
構(gòu)造方法中露戒,創(chuàng)建了一個(gè)MessageQueue(消息隊(duì)列)
final Looper me = myLooper()
的final保證了loopr的唯一性.
構(gòu)造方法源碼:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mRun = true;
mThread = Thread.currentThread();
}
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;//final了消息隊(duì)列
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); //阻塞輪詢消息
if (msg == null) {
return;
}
//其他源碼省略....
}
通過(guò)前面1,2問(wèn)題可以總結(jié)下Looper的主要作用是什么?
Looper主要作用:
- 與當(dāng)前線程綁定,保證一個(gè)線程只會(huì)有一個(gè)Looper實(shí)例捶箱,同時(shí)一個(gè)Looper實(shí)例也只有一個(gè)MessageQueue智什。
- loop()方法,不斷從MessageQueue中去取消息丁屎,交給消息的target屬性的dispatchMessage去處理荠锭。
首先Looper.prepare()在本線程中保存一個(gè)Looper實(shí)例,然后該實(shí)例中保存一個(gè)MessageQueue對(duì)象晨川;因?yàn)長(zhǎng)ooper.prepare()在一個(gè)線程中只能調(diào)用一次证九,所以MessageQueue在一個(gè)線程中只會(huì)存在一個(gè)。
從Handler中有沒(méi)學(xué)到什么好的技術(shù),或者思想?
看過(guò)MessageQueue嗎,里面的for (;;) 和while(true) 區(qū)別:
- while區(qū)別根據(jù)編譯器不同情況有所不同,例如寫死循環(huán)while(true)有的編譯器會(huì)傻傻的每次都把true做一下判斷.
- for(;;)寫死循環(huán)比較好,減少了判斷
編譯前 編譯后
while (true){todo}; mov eax,true
test eax,eax
je foo+23h
jmp foo+18h
編譯前 編譯后
for (;;){}; jmp foo+23h
一目了然共虑,for (;;)指令少愧怜,不占用寄存器,而且沒(méi)有判斷跳轉(zhuǎn)妈拌,比while (true)好叫搁。
Handler如何與MsgQueue關(guān)聯(lián)在一起?
這個(gè)問(wèn)題可以先觀察下Handler的構(gòu)造方法到底做了什么?
首先得到當(dāng)前線程中保存的Looper實(shí)例,進(jìn)而與Looper實(shí)例中的MessageQueue想綁定關(guān)聯(lián).
public Handler(Callback callback, boolean async) {
//部分源碼省略...
//獲取當(dāng)前線程保存的Looper實(shí)例
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
//通過(guò)上面mLooper獲取了實(shí)例中保存的MessageQueue,這樣兩者就綁定在一起了,關(guān)聯(lián)上.
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
使用handler發(fā)送Message的流程是什么?
sendMessage(hd) ->sendMessageAtTime(hd) ->
enqueueMessage(hd) ->dispatchMessage(looper) ->
handleMessage(looper)
那么在Activity中供炎,我們并沒(méi)有顯示的調(diào)用Looper.prepare()和Looper.loop()方法,為啥Handler可以成功創(chuàng)建呢?
這是因?yàn)樵贏ctivity的啟動(dòng)代碼中疾党,已經(jīng)在當(dāng)前UI線程ActivityThread
調(diào)用了Looper.prepare()和Looper.loop()方法音诫。
子線程里可以創(chuàng)建handler嗎?
可以的,可以在一個(gè)子線程中去創(chuàng)建一個(gè)Handler雪位,然后使用這個(gè)handler實(shí)例在任何其他線程中發(fā)送消息竭钝,最終處理消息的代碼都會(huì)在你創(chuàng)建Handler實(shí)例的線程中運(yùn)行。
詳情見(jiàn)下面實(shí)例:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare(); //創(chuàng)建Looper并與本線程綁定【第一步】
mHandler = new Handler() {
//定義并實(shí)現(xiàn)Handler.handleMessage方法【第二步】
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop(); // 啟動(dòng)Looper消息循環(huán)【第三步】
}
}
handler會(huì)不會(huì)導(dǎo)致Activity的泄漏?
- 問(wèn)題分析
Handler泄露的關(guān)鍵點(diǎn)有兩個(gè):- 內(nèi)部類
- 生命周期和Activity不一定一致
看看下面代碼
public class MainActivity extends QActivity {
//這里應(yīng)該使用static否則容易泄漏
class MyHandler extends Handler {
... ...
}
}
內(nèi)部類持有外部類Activity的引用雹洗,當(dāng)Handler對(duì)象有Message在排隊(duì)香罐,則無(wú)法釋放( 比如activity已經(jīng)destory了但是MessageQueen還有消息則,looper就會(huì)在輪詢,因此activity就無(wú)法被釋放,因內(nèi)部類有act引用),進(jìn)而導(dǎo)致Activity對(duì)象不能釋放。
- 解決方式:
如果Handler中有延遲的任務(wù)或者是等待執(zhí)行的任務(wù)隊(duì)列過(guò)長(zhǎng),都有可能因?yàn)镠andler繼續(xù)執(zhí)行而導(dǎo)致Activity發(fā)生泄漏踩窖。
此時(shí)的引用關(guān)系鏈?zhǔn)?
Looper -> MessageQueue -> Message -> Handler -> Activity
解決方案
- 可以在UI退出之前扣讼,執(zhí)行remove Handler消息隊(duì)列中的消息與runnable對(duì)象芹缔。
- 使用Static + WeakReference的方式來(lái)達(dá)到斷開(kāi)Handler與Activity之間存在引用關(guān)系的目的而咆。
主線程中的Looper.loop()一直無(wú)限循環(huán)為什么不會(huì)造成ANR吮炕?
- Activity的生命周期都是運(yùn)行在 Looper.loop() 的控制之下,當(dāng)收到不同Message時(shí)則采用相應(yīng)措施,如果它停止了,應(yīng)用也就停止了.
- Android的消息是一種事件機(jī)制,而looper.loop() 不斷地接收事件歧蒋、處理事件,只要每次處理的事件不被looper堵塞那么,就不會(huì)爆anr.
- 也就說(shuō)我們的代碼其實(shí)就是在這個(gè)循環(huán)里面去執(zhí)行的,當(dāng)然不會(huì)阻塞了.
讓我們先看一遍造成ANR的原因,就明白了
造成ANR的原因一般有兩種:
1. 當(dāng)前的事件沒(méi)有機(jī)會(huì)得到處理(即主線程正在處理前一個(gè)事件宁炫,沒(méi)有及時(shí)的完成或者looper被某種原因阻塞住了)
2. 當(dāng)前的事件正在處理偿曙,但沒(méi)有及時(shí)完成
總結(jié):
真正會(huì)卡死主線程的操作是在回調(diào)方法onCreate/onStart/onResume等操作時(shí)間過(guò)長(zhǎng),會(huì)導(dǎo)致掉幀,甚至發(fā)生ANR,而looper.loop本身不會(huì)導(dǎo)致應(yīng)用卡死.
總結(jié)
看了代碼之后,覺(jué)得它一點(diǎn)都不神秘羔巢,不就是實(shí)現(xiàn)了我們常用的“消息驅(qū)動(dòng)機(jī)制”嗎望忆?
消息驅(qū)動(dòng)機(jī)制的四要素:
1. 接收消息的“消息隊(duì)列”
2. 阻塞式地從消息隊(duì)列中接收消息并進(jìn)行處理的“線程”
3. 可發(fā)送的“消息的格式”
4. “消息發(fā)送函數(shù)”
以上四要素在Android中實(shí)現(xiàn)對(duì)應(yīng)的類如下:
1. 接收消息的“消息隊(duì)列” ——【MessageQueue】
2. 阻塞式地從消息隊(duì)列中接收消息并進(jìn)行處理的“線程” ——【Thread+Looper】
3. 可發(fā)送的“消息的格式” ——【Message<Runnable被封裝在Message中>】
4. “消息發(fā)送函數(shù)”——【Handler的post和sendMessage】