前言:
對(duì)于熟悉Android消息機(jī)制的小伙伴,可以跳到最后力穗,看主線程的消息循環(huán)转砖。
Android的消息機(jī)制主要是指Handler的運(yùn)行機(jī)制蔫仙,Handler的運(yùn)行需要底層MessageQueue和Looper的支撐。MessageQueue是采用單鏈表的數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)消息列表巨坊。 Looper會(huì)以無(wú)限循環(huán)的形式去查看是否有新消息撬槽,如果有就處理消息,否則就一直等待趾撵。ThreadLocal可以在不同線程中互不干擾的存儲(chǔ)并提供數(shù)據(jù)侄柔,通過ThreadLocal可以輕松的獲取每個(gè)線程的Looper。
10.1 Android消息機(jī)制概述
- Handler的主要作用是將某個(gè)任務(wù)切換到Handler所在的線程中去執(zhí)行占调。為什么Android要提供這個(gè)功能呢勋拟?這是因?yàn)锳ndroid規(guī)定訪問UI只能通過主線程,如果子線程訪問UI,程序會(huì)拋出異常妈候;ViewRootImpl在checkThread方法中做了判斷敢靡。
- 由于Android不建議在主線程進(jìn)行耗時(shí)操作,否則可能會(huì)導(dǎo)致ANR苦银。那我們耗時(shí)操作在子線程執(zhí)行完畢后啸胧,我們需要將一些更新UI的操作切換到主線程當(dāng)中去。所以系統(tǒng)就提供了Handler幔虏。
- 系統(tǒng)為什么不允許在子線程中去訪問UI呢纺念?
因?yàn)锳ndroid的UI控件不是線程安全的,多線程并發(fā)訪問可能會(huì)導(dǎo)致UI控件處于不可預(yù)期的狀態(tài)想括,為什么不加鎖陷谱?因?yàn)榧渔i機(jī)制會(huì)讓UI訪問邏輯變得復(fù)雜;其次鎖機(jī)制會(huì)降低UI訪問的效率,因?yàn)殒i機(jī)制會(huì)阻塞某些線程的執(zhí)行烟逊。所以Android采用了高效的單線程模型來(lái)處理UI操作渣窜。 - Handler創(chuàng)建時(shí)會(huì)采用當(dāng)前線程的Looper來(lái)構(gòu)建內(nèi)部的消息循環(huán)系統(tǒng),如果當(dāng)前線程沒有Looper就會(huì)報(bào)錯(cuò)宪躯。Handler可以通過post方法發(fā)送一個(gè)Runnable到消息隊(duì)列中乔宿,也可以通過send方法發(fā)送一個(gè)消息到消息隊(duì)列中,其實(shí)post方法最終也是通過send方法來(lái)完成访雪。
- MessageQueue的enqueueMessage方法最終將這個(gè)消息放到消息隊(duì)列中详瑞,當(dāng)Looper發(fā)現(xiàn)有新消息到來(lái)時(shí),處理這個(gè)消息臣缀,最終消息中的Runnable或者Handler的handleMessage方法就會(huì)被調(diào)用坝橡,注意Looper是運(yùn)行Handler所在的線程中的,這樣一來(lái)業(yè)務(wù)邏輯就切換到了Handler所在的線程中去執(zhí)行了精置。
10.2 Android的消息機(jī)制分析
10.2.1 ThreadLocal的工作原理
- ThreadLocal是一個(gè)線程內(nèi)部的數(shù)據(jù)存儲(chǔ)類驳庭,通過它可以在指定線程存儲(chǔ)數(shù)據(jù),數(shù)據(jù)存儲(chǔ)后氯窍,只能在指定的線程可以獲取到存儲(chǔ)的數(shù)據(jù)饲常,對(duì)于其他線程則無(wú)法獲取到數(shù)據(jù)。一般來(lái)說(shuō)狼讨,當(dāng)數(shù)據(jù)是以線程作為作用域并且不同線程有不同副本的時(shí)候贝淤,就可以考慮使用ThreadLocal。對(duì)于Handler來(lái)說(shuō)政供,它需要獲取當(dāng)前線程的Looper,而Looper的作用于就是線程并且不同的線程具有不同的Looper播聪,通過ThreadLocal可以輕松實(shí)現(xiàn)線程中的存取。
- ThreadLocal的另一個(gè)使用場(chǎng)景是復(fù)雜邏輯下的對(duì)象傳遞布隔。
-
ThreadLocal原理:不同線程訪問同一個(gè)ThreadLoacl的get方法离陶,ThreadLocal的get方法會(huì)從各自的線程中取出一個(gè)數(shù)組,然后再?gòu)臄?shù)組中根據(jù)當(dāng)前ThreadLocal的索引去查找對(duì)應(yīng)的Value值衅檀。
(1) ThreadLocal set方法
//PS:JDK中與Android SDK的ThreadLocal有些許區(qū)別招刨,我們以SDK中的為例
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
獲取到當(dāng)前線程,并從當(dāng)前線程中取出對(duì)應(yīng)的ThreadLocalMap(內(nèi)部由Entry[] table來(lái)存儲(chǔ)ThreadLoacl的值)哀军,再把相應(yīng)數(shù)據(jù)存入其中沉眶。如果ThreadLocalMap為空,則創(chuàng)建該線程的ThreadLocalMap對(duì)象杉适,再將ThreadLocal的值進(jìn)行存儲(chǔ)谎倔。
(2) ThreadLocal get方法
public T get() {
// Optimized for the fast path.
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values != null) {
Object[] table = values.table;
int index = hash & values.mask;
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
values = initializeValues(currentThread);
}
return (T) values.getAfterMiss(this);
}
獲取當(dāng)前線程,并從當(dāng)前線程中獲取對(duì)應(yīng)的ThreadLocalMap猿推,通過key獲取對(duì)應(yīng)的value片习;如果未獲取到對(duì)應(yīng)的ThreadLocalMap,則創(chuàng)建并將該對(duì)象返回。
(3)ThreadLocal的值在table數(shù)值中的位置總是ThreadLocal的索引+1藕咏。
10.2.2 消息隊(duì)列的工作原理
- MessageQueue主要有兩個(gè)操作状知,插入和讀取,讀取操作伴隨著刪除操作侈离;MessageQueue是通過單鏈表的數(shù)據(jù)結(jié)構(gòu)來(lái)維護(hù)消息列表的。
- enqueueMessage方法的作用是往消息隊(duì)列插入一條消息筝蚕。next方法是一個(gè)無(wú)線循環(huán)的方法卦碾,如果消息隊(duì)列中沒有消息,那么next方法會(huì)一直阻塞在這里起宽。當(dāng)有新消息到來(lái)時(shí)洲胖,next方法會(huì)返回這條消息并將其從單鏈表中移除。
10.2.3 Looper的工作原理
- prepareMainLooper方法主要給主線程也就是ActivityThread創(chuàng)建Looper使用的坯沪,本質(zhì)也是通過prepare方法實(shí)現(xiàn)的绿映。
-
Looper提供quit和quitSafely來(lái)退出一個(gè)Looper,區(qū)別在于quit會(huì)直接退出Looper腐晾,而quitSafely會(huì)把消息隊(duì)列中已有的消息處理完畢后才安全地退出叉弦。
Looper退出后,這時(shí)候通過Handler發(fā)送的消息會(huì)失敗藻糖,Handler的send方法會(huì)返回false淹冰。
在子線程中,如果手動(dòng)為其創(chuàng)建了Looper巨柒,在所有事情做完后樱拴,應(yīng)該調(diào)用Looper的quit方法來(lái)終止消息循環(huán),否則這個(gè)子線程就會(huì)一直處于等待狀態(tài)洋满;而如果退出了Looper以后晶乔,這個(gè)線程就會(huì)立刻終止,因此建議不需要的時(shí)候終止Looper牺勾。 - loop方法會(huì)調(diào)用MessageQueue的next方法來(lái)獲取新消息正罢,而next是是一個(gè)阻塞操作,但沒有信息時(shí)驻民,next方法會(huì)一直阻塞在那里腺怯,這也導(dǎo)致loop方法一直阻塞在那里。如果MessageQueue的next方法返回了新消息川无,Looper就會(huì)處理這條消息:mas.target.dispatchMessage(msg),這里的msg.target是發(fā)送這條消息的Handler對(duì)象呛占,這樣Handler發(fā)送的消息最終又交給Handler來(lái)處理了。
10.2.4 Handler的工作原理
- Handler的工作主要包含消息的發(fā)送和接受過程懦趋。發(fā)送過程通過post的一系列方法和send的一系列方法來(lái)實(shí)現(xiàn)晾虑。Handler發(fā)送過程僅僅是向消息隊(duì)列中插入了一條消息。MessageQueue的next方法就會(huì)返回這條消息給Looper,Looper拿到這條消息就開始處理帜篇,最終消息會(huì)交給Handler來(lái)處理糙捺。
- Handler處理消息
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
//Message的callback是一個(gè)Runnable,
//也就是Handler的 post方法所傳遞的Runnable參數(shù)
handleCallback(msg);
} else {
//如果給Handler設(shè)置了Callback的實(shí)現(xiàn),
//則調(diào)用Callback的handleMessage(msg)
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//調(diào)用Handler的handleMessage方法來(lái)處理消息笙隙,
//該Handler子類需重寫handlerMessage(msg)方法
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
public interface Callback {
public boolean handleMessage(Message msg);
}
//默認(rèn)空實(shí)現(xiàn)
public void handleMessage(Message msg) {
}
- Handler還有一個(gè)特殊的構(gòu)造方法洪灯,可以指定一個(gè)特殊的Looper來(lái)構(gòu)造Handler。
public Handler(Looper looper) {
this(looper, null, false);
}
- Handler創(chuàng)建需要Looper竟痰,否則會(huì)拋出異常签钩,默認(rèn)獲取當(dāng)前線程的Looper。主線程也就是ActivityThread會(huì)自動(dòng)創(chuàng)建Looper坏快,其他線程如果需要Looper均需要手動(dòng)創(chuàng)建铅檩。
10.3 主線程消息循環(huán)
- Android的主線程就是ActivityThread,主線程的入口方法為main莽鸿,在main方法中系統(tǒng)會(huì)通過Looper.prepareMainLooper()來(lái)創(chuàng)建主線程的Looper以及MessageQueue昧旨,并通過Looper.loop()來(lái)開啟主線程的消息循環(huán)。
public static void main(String[] args) {
...
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
AsyncTask.init();
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
- Looper.loop()祥得,這里是一個(gè)死循環(huán)兔沃,如果主線程的Looper終止,則應(yīng)用程序會(huì)拋出異常级及。那么問題來(lái)了粘拾,既然主線程卡在這里了,(1)那Activity為什么還能啟動(dòng)创千;(2)點(diǎn)擊一個(gè)按鈕仍然可以響應(yīng)缰雇?
問題1:startActivity的時(shí)候,會(huì)向AMS(ActivityManagerService)發(fā)一個(gè)跨進(jìn)程請(qǐng)求(AMS運(yùn)行在系統(tǒng)進(jìn)程中)追驴,之后AMS啟動(dòng)對(duì)應(yīng)的Activity械哟;AMS也需要調(diào)用App中Activity的生命周期方法(不同進(jìn)程不可直接調(diào)用),AMS會(huì)發(fā)送跨進(jìn)程請(qǐng)求殿雪,然后由App的ActivityThread中的ApplicationThread會(huì)來(lái)處理暇咆,ApplicationThread會(huì)通過主線程線程的Handler將執(zhí)行邏輯切換到主線程。重點(diǎn)來(lái)了丙曙,主線程的Handler把消息添加到了MessageQueue爸业,Looper.loop會(huì)拿到該消息,并在主線程中執(zhí)行亏镰。這就解釋了為什么主線程的Looper是個(gè)死循環(huán)扯旷,而Activity還能啟動(dòng),因?yàn)樗拇蠼M件的生命周期都是以消息的形式通過UI線程的Handler發(fā)送索抓,由UI線程的Looper執(zhí)行的钧忽。
問題2:和問題1原理一樣毯炮,最終都是由系統(tǒng)發(fā)消息來(lái)處理的,都經(jīng)過了Looper.loop()耸黑。
問題2詳細(xì)分析請(qǐng)看原書作者的Android中MotionEvent的來(lái)源和ViewRootImpl
關(guān)于我
簡(jiǎn)書:HuDP
WeChat:mox1103
WeiBo:HuDP_
歡迎各位小伙伴留言交流[開心臉]