Handler
在android開發(fā)中可謂隨處可見吏垮,不論你是一個(gè)剛開始學(xué)習(xí)android的新人在扰,還是昔日的王者,都離不開它笼痛。關(guān)于 handler
的源碼已經(jīng)很前人分享過了。如果我沒能給大家講明白可以參考網(wǎng)上其他人寫的琅拌。
注:文中所貼源碼都有精簡(jiǎn)過缨伊,并非完整源碼,只保留主思路进宝,刪減了一些非法校驗(yàn)細(xì)節(jié)實(shí)現(xiàn)
目錄
- 簡(jiǎn)單使用方法
- 源碼流程分析
簡(jiǎn)單使用方法
應(yīng)用層開發(fā)時(shí)handle
常要用于線程切換調(diào)度和異步消息刻坊、更新UI等,但不僅限于這些党晋。
使用方法:略
哇哈哈哈谭胚,不要打我。為了不占用篇幅隶校,想必識(shí)標(biāo)題來者理當(dāng)熟悉漏益。若有不明之處且看其他偏基礎(chǔ)點(diǎn)的教程便可。
源碼流程分析
大王深胳,且先隨我看小的從網(wǎng)上盜來的一張圖绰疤。handler
發(fā)送Message
(消息)至MessageQueue
(模擬隊(duì)列),由Looper
(循環(huán)器)不斷循環(huán)取出舞终。然后通知Handler
處理轻庆。這便是整個(gè)的消息機(jī)制。沒有多復(fù)雜敛劝。
關(guān)鍵對(duì)象源碼分析:
- Looper 消息輪訓(xùn)器
- MessageQueue 消息暫存隊(duì)列(單鏈表結(jié)構(gòu))
- Message 消息
- Handler 收發(fā)消息工具
- ThreadLocal (本地線程數(shù)據(jù)存儲(chǔ)對(duì)象)
ThreadLocal
先說ThreadLocal
的作用是不同的線程擁有該線程獨(dú)立的變量余爆,同名對(duì)象不會(huì)被受到不同線程間相互使用出現(xiàn)異常的情況。
即:你的程序擁有多個(gè)線程夸盟,線程中要用到相同的對(duì)象蛾方,但又不允許線程之間操作同一份對(duì)象。那么就可以使用ThreadLocal
來解決上陕。它可以在線程中使用mThreadLocal.get()
和mThreadLocal.set()
來使用桩砰。若未被在當(dāng)前線程中調(diào)用set
方法,那么get
時(shí)為空释簿。
在Looper中是一個(gè)靜態(tài)變量的形式存在亚隅,并在每個(gè)線程中擁有獨(dú)立的Looper對(duì)象,沒有則為空庶溶。
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
如若還不明白可單獨(dú)做了解煮纵,弄明白這個(gè)是必須的懂鸵,否則后面會(huì)云里霧里。不知為何hanlder
可以做到跨線程消息切換行疏。我姑且當(dāng)做讀者已熟悉這點(diǎn)匆光。
這里Looper
是最為重要的一環(huán),我們先來看這個(gè)隘擎,其余幾個(gè)對(duì)象源碼分析的意義不大殴穴,后面小節(jié)會(huì)在消息流程中分析到。就省略了货葬。如果非要糾結(jié)解析可以自己去翻閱一下源碼即可采幌。
Looper 關(guān)鍵源碼
記住了,Looper
主要做兩件事震桶。1.創(chuàng)建消息隊(duì)列休傍。 2.循環(huán)取隊(duì)列中的消息分發(fā)。
后面一個(gè)小節(jié)會(huì)講什么時(shí)候創(chuàng)建蹲姐,見流程分析磨取。
構(gòu)造函數(shù)
在 Looer
創(chuàng)建的時(shí)候初始化了MessageQueue
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);//創(chuàng)建消息隊(duì)列
mThread = Thread.currentThread(); //獲取當(dāng)前線程
}
**創(chuàng)建Looper **
其中分為在主線程中創(chuàng)建,和子線程創(chuàng)建柴墩,但都是借助ThreadLocal
來實(shí)現(xiàn)跨線程Looper
對(duì)象的存儲(chǔ)忙厌。
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án)
可以看到loop方法
中是一個(gè)死循環(huán)在不斷的取消息。但注意當(dāng)無消息時(shí)循環(huán)并不會(huì)做無用功江咳,而是阻塞等待消息逢净。
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // 取出消息,無消息則阻塞
if (msg == null) { return; }
msg.target.dispatchMessage(msg);//發(fā)送消息 其中target就是Handler
}
}
消息流程分解:
主線程中Looper的創(chuàng)建
1.
ActivityThread
創(chuàng)建在main方法
中調(diào)用Looper.prepareMainLooper();
創(chuàng)建出Looper
歼指,而后將創(chuàng)建的Looper
存于線程變量中(ThreadLocal
)爹土,再將主線程中的Looper
單獨(dú)存一份,因?yàn)樗侵骶€程的Looper
踩身。(實(shí)際它調(diào)用的是Looper.prepare()
,我們也可以在子線程中使用時(shí)胀茵,用它來創(chuàng)建Looper
)。2.在
main方法
的最后調(diào)用Looper.loop();
來開啟循環(huán)挟阻。
public static void main(String[] args) {
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
Looper.loop();
}
這便是琼娘,為什么handler
可以做異步的原因了,因?yàn)樵谥鱑I線程創(chuàng)建的時(shí)候附鸽,就早已為UI線程創(chuàng)建了一個(gè)Looper
脱拼,并開啟了循環(huán)。
注:請(qǐng)思考一個(gè)問題拒炎,能不能在子線程中直接new handler
發(fā)送消息挪拟?如果不可以挨务?有沒有辦法解決击你?(切莫去搜一下看到某行代碼添加完就可以了便不管為什么這樣了玉组,應(yīng)當(dāng)分析內(nèi)部原理。)
handler如何收發(fā)消息
- 1.
new Handler()
時(shí)構(gòu)造方法從Looper.myLooper();
獲取當(dāng)前線程中的Looper
對(duì)象丁侄,然后取出MessageQueue
對(duì)象,以備后面發(fā)消息用惯雳。
public Handler(Callback callback, boolean async) {
mLooper = Looper.myLooper();
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
- 2
handler
通過sendMessage(msg)
將消息發(fā)出,消息最終走向queue.enqueueMessage(msg, uptimeMillis);
這里的queque
便是我們前面從handler
構(gòu)造方法中Looper
里取到消息隊(duì)列鸿摇。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
return queue.enqueueMessage(msg, uptimeMillis);
}
- 3.
enqueueMessage方法
里其中還會(huì)將當(dāng)前發(fā)消息的handler
存于msg
的target
中石景。當(dāng)Looper
輪訓(xùn)到這條消息時(shí),便會(huì)使用到拙吉。我們往下再看一眼之前Looper.loop()
方法潮孽。最終調(diào)用了msg.target.dispatchMessage(msg);
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // 取出消息,無消息則阻塞
if (msg == null) { return; }
msg.target.dispatchMessage(msg);//發(fā)送消息 其中target就是Handler
}
}
4.自此dispatchMessage()
中調(diào)用handleMessage(msg);
回調(diào)筷黔。消息就送達(dá)了往史。收工。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
再來看看這幅圖佛舱,按照我上面的思路流程在跟著圖走一走椎例。看看我們分析得是否正確请祖。
最后附一張以前學(xué)習(xí)Hanlder手寫筆記订歪,其實(shí)這份筆記光看的話,可能對(duì)讀者沒什么很大做用肆捕。但對(duì)我的幫助很大刷晋。我要表達(dá)的是一個(gè)學(xué)習(xí)思路,像類似這種源碼分析最好自己拿筆寫寫畫畫福压,映象會(huì)深刻很多掏秩。知識(shí)過久了會(huì)忘記,光靠死記若非常人荆姆,很難過目不忘蒙幻。自己寫一遍就完全不一樣了,就算過了許久已淡忘這些胆筒,打開自己的筆記看一眼就會(huì)明白邮破。而不用從頭來學(xué)一遍。
用我的理解來解釋這種現(xiàn)象是學(xué)習(xí)的過程中可能坑坑洼洼仆救,消磨掉不少時(shí)間抒和。這種筆記會(huì)成為最后總結(jié)出來的結(jié)晶,與腦子里的印象流關(guān)聯(lián)在一起彤蔽。何必再費(fèi)力氣每次都從頭溫習(xí)摧莽,不如直接看以前自己的總結(jié)豈不快哉?
先生曰:小伙子長(zhǎng)得眉目清新秀顿痪,這字真是丑得像雞爪子爬镊辕,各位受委屈了油够。
下一篇我們將分解HandlerThread的工作原理和做用。
本文參考:
AndroidFramework官方源碼
Handler 之 源碼解析
如何下次找到我?
- 關(guān)注我的簡(jiǎn)書
- 本篇同步Github倉(cāng)庫(kù):https://github.com/BolexLiu/DevNote (可以關(guān)注)