理解 Activity.runOnUiThread
在開(kāi)發(fā) Android 應(yīng)用的時(shí)候我們總是要記住應(yīng)用主線程啡直。
主線程非常繁忙,因?yàn)樗幚砝L制UI尿这,響應(yīng)用戶的交互只磷,默認(rèn)情況下執(zhí)行我們寫(xiě)下的大部分代碼。
好的開(kāi)發(fā)者知道他/她需要將重負(fù)荷的任務(wù)移除到工作線程避免主線程阻塞名惩,同時(shí)獲得更流暢的用戶體驗(yàn)澎胡,避免ANR的發(fā)生。
但是娩鹉,當(dāng)需要更新UI的時(shí)候我們需要“返回”到主線程攻谁,因?yàn)橹挥兴趴梢愿聭?yīng)用 UI。
最常用的方式是調(diào)用 Activity 的 runOnUiThread()
方法:
runOnUiThread(new Runnable() {
void run() {
// Do stuff…
}
});
這樣就可以神奇的將 Runnable 任務(wù)放到主線程中執(zhí)行弯予。
魔法是很棒戚宦。。锈嫩。但是它存在與我們的應(yīng)用源碼之外受楼。在本文中,我將嘗試闡述runOnUiThread()
中發(fā)生的一切呼寸,并且(希望)能夠破解魔法艳汽。
破解魔法
我們一起來(lái)看看 Activity 源碼中的相關(guān)部分:
final Handler mHandler = new Handler();
private Thread mUiThread;
// ...
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
// ...
}
看起來(lái)非常簡(jiǎn)單,首先我們檢查當(dāng)前運(yùn)行的線程是否是主線線程对雪。
如果是主線程--很棒河狐!只需要調(diào)用 Runnable 的 run()
方法。
但是如果不是主線程呢瑟捣?
在這種情況下馋艺,我們會(huì)調(diào)用 mHandler.post()
并將我們的 Runnable 傳遞過(guò)去。所以究竟發(fā)生了什么事情迈套?
在回答這個(gè)問(wèn)題之前我們真的需要討論一下一個(gè)稱為 Looper 的東西捐祠。
一切都從 Looper 開(kāi)始
當(dāng)我們創(chuàng)建一個(gè)新的 Java 線程時(shí),我們重寫(xiě)它的 run()
方法桑李。一個(gè)簡(jiǎn)單的線程實(shí)現(xiàn)看起來(lái)應(yīng)該是這樣的:
public class MyThread extends Thread {
@Override
public void run() {
// Do stuff...
}
}
好好的看一下 run()
方法踱蛀,當(dāng)線程執(zhí)行完該方法中所有的語(yǔ)句后,線程就完成了芙扎。結(jié)束了。沒(méi)用了填大。
如我我們想重復(fù)使用一個(gè)線程(一個(gè)很好的理由就是避免新線程創(chuàng)建以及減少內(nèi)存消耗)我們必須讓它保持存活狀態(tài)并且等待接收新的指令戒洼。一個(gè)常用的方式就是在線程的 run()
方法里創(chuàng)建一個(gè)循環(huán):
public class MyThread extends Thread {
private boolean running;
@Override
public void run() {
while (running) {
// Do stuff...
}
}
}
只要 while 循環(huán)還在執(zhí)行(即 run()
方法還沒(méi)有執(zhí)行完畢)--這個(gè)線程就保持存活狀態(tài)。
這就是 Looper 所做的事情:
Looper允华。圈浇。寥掐。就是 LOOPING,并保持它的線程處于存活狀態(tài)
關(guān)于 Looper 以下幾點(diǎn)值得注意:
- 線程默認(rèn)沒(méi)有 Looper
- 你可創(chuàng)建一個(gè) Looper 并將它綁定到一個(gè)線程
- 每一個(gè)線程只能綁定一個(gè) Looper
所以磷蜀,我們將線程中的 while 循環(huán)用 Looper 實(shí)現(xiàn)來(lái)替換:
public class MyThread extends Thread {
@Override
public void run() {
Looper.prepare();
Looper.loop();
}
}
真的很簡(jiǎn)單:
調(diào)用 Lopper.prepare()
是檢查當(dāng)前線程是否還沒(méi)有綁定 Lopper(記住召耘,每一個(gè)線程只能綁定一個(gè) Looper),如果沒(méi)有就創(chuàng)建一個(gè) Looper 并和當(dāng)前線程綁定褐隆。
調(diào)用 Looper.loop()
觸發(fā)我們的 Looper 開(kāi)始循環(huán)污它。
所以,現(xiàn)在 Looper 開(kāi)始循環(huán)并保持線程處于存活狀態(tài)庶弃,但是如果不能傳遞指令衫贬、任務(wù)或者其他事情讓線程執(zhí)行實(shí)際的任務(wù),那么保持線程存活沒(méi)有任何意義歇攻。固惯。。
幸好缴守,Looper 不僅僅是循環(huán)葬毫。
當(dāng)我們創(chuàng)建 Looper 的時(shí)候,會(huì)一并創(chuàng)建一個(gè)工作隊(duì)列屡穗。這個(gè)隊(duì)列稱為消息隊(duì)列因?yàn)樗钟邢ⅲ?strong>Message)對(duì)象贴捡。
消息是什么?
這些消息對(duì)象實(shí)際上就是一系列指令鸡捐。
他們可以持有數(shù)據(jù)比如字符串栈暇、整數(shù)等,也可以只有任務(wù)比如 Runnerables箍镜。
所以源祈,當(dāng)一個(gè)消息進(jìn)入線程的 Looper消息隊(duì)列,并且輪到它(畢竟它是一個(gè)隊(duì)列)的時(shí)候--消息指令就會(huì)在隊(duì)列所在的線程執(zhí)行色迂。這意味著香缺。。歇僧。图张。:
如果我們希望一個(gè) Runnable 在指定的線程運(yùn)行,我們只需要將它放到一個(gè)消息里诈悍,并將這個(gè)消息放到對(duì)應(yīng)線程的 Looper 消息隊(duì)列就可以了祸轮!
很棒!我們?cè)趺磳?shí)現(xiàn)呢侥钳?
很簡(jiǎn)單适袜。我們使用 Handler。
Handler
Handler 干了所有的活舷夺。
它負(fù)責(zé)向 Looper 的隊(duì)列添加消息苦酱,當(dāng)輪到消息執(zhí)行時(shí)售貌,它負(fù)責(zé)在 Looper 所在的線程中執(zhí)行同一條消息。
當(dāng)一個(gè) Handler 被創(chuàng)建的時(shí)候疫萤,會(huì)被指向一個(gè)指定的 Looper(即颂跨,指向一個(gè)指定的線程)
創(chuàng)建 Handler 有兩種方法:
-
1、在構(gòu)造函數(shù)中指定 Looper:
Handler handler = new Handler(Looper looper);
現(xiàn)在 handler指向了我們提供的Looper(實(shí)際上是 Looper 的消息隊(duì)列)
-
2扯饶、使用空的構(gòu)造函數(shù):
Handler handler = new Handler();
當(dāng)我們使用空構(gòu)造函數(shù)的時(shí)候恒削,Handler 會(huì)自動(dòng)指向和當(dāng)前線程綁定的 Looper。真方便帝际!
Handler 提供了很方便的方法用于創(chuàng)建消息并自動(dòng)將它們添加到 Looper 消息隊(duì)列蔓同。
例如,post()
方法就創(chuàng)建一條消息并將它添加到 Looper 隊(duì)列的尾部蹲诀。
如果我們希望消息持有一個(gè)任務(wù)(一個(gè) Runnable)斑粱,我們簡(jiǎn)單的將 Runnable 對(duì)象傳遞給 post()
方法就可以:
handler.post(new Runnable() {
@Override
public void run() {
// Do stuff...
}
});
看起來(lái)很熟悉?
再來(lái)看看 Activity 的源碼
現(xiàn)在我們?cè)僮屑?xì)的看一看runOnUiThread():
final Handler mHandler = new Handler();
private Thread mUiThread;
// ...
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
// ...
}
首先脯爪,mHandler 是使用空構(gòu)造函數(shù)創(chuàng)建则北。
記追谩:這段代碼是在主線程中執(zhí)行辈毯,這意味著 mHandler
指向主線程的 Looper。
是的摆出,應(yīng)用主線程是唯一一個(gè)默認(rèn)綁定了 Looper 線程掖举。
所以快骗。。塔次。當(dāng)這一行代碼執(zhí)行的時(shí)候:
mHandler.post(action);
Handler 會(huì)創(chuàng)建一條持有我們傳入的 Runnable 的消息方篮,這條消息隨后被添加到主線程的消息隊(duì)列,然后等待 Handler 在它的Looper線程(主線程)中執(zhí)行励负。
就是這樣藕溅!魔力不再。