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

Handler在android開發中可謂隨處可見,不論你是一個剛開始學習android的新人,還是昔日的王者,都離不開它。關于 handler的源碼已經很前人分享過了。如果我沒能給大家講明白可以參考網上其他人寫的。

注:文中所貼源碼都有精簡過,并非完整源碼,只保留主思路,刪減了一些非法校驗細節實現

目錄

  • 簡單使用方法
  • 源碼流程分析

簡單使用方法

應用層開發時handle常要用于線程切換調度和異步消息、更新UI等,但不僅限于這些。

使用方法:略

哇哈哈哈,不要打我。為了不占用篇幅,想必識標題來者理當熟悉。若有不明之處且看其他偏基礎點的教程便可。


源碼流程分析

大王,且先隨我看小的從網上盜來的一張圖。handler發送Message(消息)至MessageQueue(模擬隊列),由Looper(循環器)不斷循環取出。然后通知Handler處理。這便是整個的消息機制。沒有多復雜。

關鍵對象源碼分析:

  • Looper 消息輪訓器
  • MessageQueue 消息暫存隊列(單鏈表結構)
  • Message 消息
  • Handler 收發消息工具
  • ThreadLocal (本地線程數據存儲對象)

ThreadLocal

先說ThreadLocal的作用是不同的線程擁有該線程獨立的變量,同名對象不會被受到不同線程間相互使用出現異常的情況。

即:你的程序擁有多個線程,線程中要用到相同的對象,但又不允許線程之間操作同一份對象。那么就可以使用ThreadLocal來解決。它可以在線程中使用mThreadLocal.get()mThreadLocal.set()來使用。若未被在當前線程中調用set方法,那么get時為空。

在Looper中是一個靜態變量的形式存在,并在每個線程中擁有獨立的Looper對象,沒有則為空。
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
如若還不明白可單獨做了解,弄明白這個是必須的,否則后面會云里霧里。不知為何hanlder可以做到跨線程消息切換。我姑且當做讀者已熟悉這點。
這里Looper是最為重要的一環,我們先來看這個,其余幾個對象源碼分析的意義不大,后面小節會在消息流程中分析到。就省略了。如果非要糾結解析可以自己去翻閱一下源碼即可。

Looper 關鍵源碼

記住了,Looper主要做兩件事。1.創建消息隊列。 2.循環取隊列中的消息分發。后面一個小節會講什么時候創建,見流程分析。

構造函數
Looer創建的時候初始化了MessageQueue

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);//創建消息隊列
        mThread = Thread.currentThread(); //獲取當前線程
    }

**創建Looper **
其中分為在主線程中創建,和子線程創建,但都是借助ThreadLocal來實現跨線程Looper對象的存儲。

    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));
    }

開啟循環
可以看到loop方法中是一個死循環在不斷的取消息。但注意當無消息時循環并不會做無用功,而是阻塞等待消息。

    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);//發送消息 其中target就是Handler
        }
    }

消息流程分解:

主線程中Looper的創建

  • 1.ActivityThread創建在main方法中調用Looper.prepareMainLooper();創建出Looper,而后將創建的Looper存于線程變量中(ThreadLocal),再將主線程中的Looper單獨存一份,因為他是主線程的Looper。(實際它調用的是Looper.prepare(),我們也可以在子線程中使用時,用它來創建Looper)。

  • 2.在main方法的最后調用Looper.loop();來開啟循環。

    public static void main(String[] args) {
        Looper.prepareMainLooper();
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
        Looper.loop();
    }

這便是,為什么handler可以做異步的原因了,因為在主UI線程創建的時候,就早已為UI線程創建了一個Looper,并開啟了循環。

注:請思考一個問題,能不能在子線程中直接new handler發送消息?如果不可以?有沒有辦法解決?(切莫去搜一下看到某行代碼添加完就可以了便不管為什么這樣了,應當分析內部原理。)

handler如何收發消息

  • 1.new Handler()時構造方法從Looper.myLooper();獲取當前線程中的Looper對象,然后取出MessageQueue對象,以備后面發消息用。

 public Handler(Callback callback, boolean async) {
        mLooper = Looper.myLooper();
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
  • 2 handler通過sendMessage(msg)將消息發出,消息最終走向queue.enqueueMessage(msg, uptimeMillis);這里的queque便是我們前面從handler構造方法中Looper里取到消息隊列。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        return queue.enqueueMessage(msg, uptimeMillis);
    }
  • 3.enqueueMessage方法里其中還會將當前發消息的handler存于msgtarget中。當Looper輪訓到這條消息時,便會使用到。我們往下再看一眼之前Looper.loop()方法。最終調用了 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);//發送消息 其中target就是Handler
        }
    }

4.自此dispatchMessage() 中調用handleMessage(msg);回調。消息就送達了。收工。

   public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

再來看看這幅圖,按照我上面的思路流程在跟著圖走一走。看看我們分析得是否正確。

最后附一張以前學習Hanlder手寫筆記,其實這份筆記光看的話,可能對讀者沒什么很大做用。但對我的幫助很大。我要表達的是一個學習思路,像類似這種源碼分析最好自己拿筆寫寫畫畫,映象會深刻很多。知識過久了會忘記,光靠死記若非常人,很難過目不忘。自己寫一遍就完全不一樣了,就算過了許久已淡忘這些,打開自己的筆記看一眼就會明白。而不用從頭來學一遍。

用我的理解來解釋這種現象是學習的過程中可能坑坑洼洼,消磨掉不少時間。這種筆記會成為最后總結出來的結晶,與腦子里的印象流關聯在一起。何必再費力氣每次都從頭溫習,不如直接看以前自己的總結豈不快哉?


先生曰:小伙子長得眉目清新秀,這字真是丑得像雞爪子爬,各位受委屈了。

下一篇我們將分解HandlerThread的工作原理和做用。

本文參考:
AndroidFramework官方源碼
Handler 之 源碼解析


如何下次找到我?

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,250評論 6 530
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 97,923評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,041評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,475評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,253評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,801評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,882評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,023評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,530評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,494評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,639評論 1 366
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,177評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,890評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,289評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,552評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,242評論 3 389
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,626評論 2 370

推薦閱讀更多精彩內容