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