理解 Activity.runOnUiThread

理解 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í)行励负。

就是這樣藕溅!魔力不再。

本文譯自: Understanding Activity.runOnUiThread()

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末继榆,一起剝皮案震驚了整個(gè)濱河市巾表,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌略吨,老刑警劉巖集币,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異翠忠,居然都是意外死亡鞠苟,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)偶妖,“玉大人,你說(shuō)我怎么就攤上這事政溃≈悍茫” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵董虱,是天一觀的道長(zhǎng)扼鞋。 經(jīng)常有香客問(wèn)我,道長(zhǎng)愤诱,這世上最難降的妖魔是什么云头? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮淫半,結(jié)果婚禮上溃槐,老公的妹妹穿的比我還像新娘。我一直安慰自己科吭,他們只是感情好昏滴,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著对人,像睡著了一般谣殊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上牺弄,一...
    開(kāi)封第一講書(shū)人閱讀 51,443評(píng)論 1 302
  • 那天姻几,我揣著相機(jī)與錄音,去河邊找鬼势告。 笑死蛇捌,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的培慌。 我是一名探鬼主播豁陆,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼吵护!你這毒婦竟也來(lái)了盒音?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤馅而,失蹤者是張志新(化名)和其女友劉穎祥诽,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體瓮恭,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡雄坪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了屯蹦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片维哈。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡绳姨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出阔挠,到底是詐尸還是另有隱情飘庄,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布购撼,位于F島的核電站跪削,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏迂求。R本人自食惡果不足惜碾盐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望揩局。 院中可真熱鬧毫玖,春花似錦、人聲如沸凌盯。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)十气。三九已至励背,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間砸西,已是汗流浹背叶眉。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留芹枷,地道東北人衅疙。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像鸳慈,于是被迫代替她去往敵國(guó)和親饱溢。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容