Handler消息源碼流程分析(含手寫筆記)

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存于msgtarget中石景。當(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 之 源碼解析


如何下次找到我?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末征懈,一起剝皮案震驚了整個(gè)濱河市石咬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌卖哎,老刑警劉巖鬼悠,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異亏娜,居然都是意外死亡焕窝,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門维贺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來袜啃,“玉大人,你說我怎么就攤上這事幸缕∪悍ⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵发乔,是天一觀的道長(zhǎng)熟妓。 經(jīng)常有香客問我,道長(zhǎng)栏尚,這世上最難降的妖魔是什么起愈? 我笑而不...
    開封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮译仗,結(jié)果婚禮上抬虽,老公的妹妹穿的比我還像新娘。我一直安慰自己纵菌,他們只是感情好阐污,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著咱圆,像睡著了一般笛辟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上序苏,一...
    開封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天手幢,我揣著相機(jī)與錄音,去河邊找鬼忱详。 笑死围来,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播监透,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼钦铁,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了才漆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤佛点,失蹤者是張志新(化名)和其女友劉穎醇滥,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體超营,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鸳玩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了演闭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片不跟。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖米碰,靈堂內(nèi)的尸體忽然破棺而出窝革,到底是詐尸還是另有隱情,我是刑警寧澤吕座,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布虐译,位于F島的核電站,受9級(jí)特大地震影響吴趴,放射性物質(zhì)發(fā)生泄漏漆诽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一锣枝、第九天 我趴在偏房一處隱蔽的房頂上張望厢拭。 院中可真熱鬧,春花似錦撇叁、人聲如沸供鸠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)回季。三九已至,卻和暖如春正林,著一層夾襖步出監(jiān)牢的瞬間泡一,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工觅廓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鼻忠,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像帖蔓,于是被迫代替她去往敵國(guó)和親矮瘟。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

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