Android Handler消息機制源碼分析

概述

Handler通常被我們開發者用來從子線程中發送消息通知主線程跟新UI,但是這只是Handle的冰山一角,本文從源碼的角度徹底解析Handler消息機制的原理。
Handler機制只要涉及的幾個類:


01.png

主要從以下方面來分析整個源碼:

  • Handler、Looper、MessageQueue、Message的源碼
  • mainThread 的 handler 和 looper(從ActivityThread.main開始)
  • 整個消息發送的流程
  • 分析每個環節的代碼細節
  • Handler、Looper和Thread之間的關系
  • 由handler造成的內存泄露問題

整個消息發送、接收流程的大致梳理

02.png

第一步:handle.sendMessage()把消息發送給對應線程的Looper的消息列隊(每個looper持有一個MessageQueue)
第二步:Looer.loop()不斷輪詢取出消息
第三步:取出消息后發送給對應的Handler


系統主線程中的Handler

Handler在Android的整個架構中間不僅僅只是負責更新UI的作用,通過分析AcitivityThread的源碼可以看到,系統一開始幫我們初始化一個主線程的Handler和Looper,Activity、Service等的生命周期的消息都是經過Handler來處理的,可見Handler的重要性。我們都知道Java的程序入口是main函數,Android自然也是一樣

AcitivityThread#main
final H mH = new H();
public static void main(String[] args) {
    ...
    //在main線程里初始化一個Looper
    Looper.prepareMainLooper();
    //開啟死循環,不斷輪詢MessageQueue一旦有消息,從列隊中取出發送
    Looper.loop();
    ...
}

private class H extends Handler {
}

首先調用Looper.prepareMainLooper(),初始化一個Looper將Looper和main線程綁定,然后在AcitivityThread里面懶加載了一個H變量,H繼承的Handler,最后Looper.loop()開啟死循環,不斷輪詢MessageQueue一旦有消息,從列隊中取出發送。
接下來看看H.handleMessage()干了哪些事情,抽取幾個分析:

public void handleMessage(Message msg) {
      ...
      case BIND_SERVICE://綁定服務
      ...
      case PAUSE_ACTIVITY://Activity的生命周期控制
      ...
      case EXIT_APPLICATION://退出應用
              Looper.myLooper().quit();
              break;
}

可以看到Android系統的消息機制、事件反饋機制等的消息都是由Handler來進行分發的。
看到這里可能大家會有這么一個疑問,那就是看到AcitivityThread.main方法里面就寥寥幾行代碼,main方法運行完了整個App程序不也就退出了嗎?為什么App能夠一直存在呢?因為Looper.loop()里面執行了一個死循環。循環退出了,main函數執行完了,程序也就退出了,這就是上面Looper.myLooper().quit()退出looper的死循環就退出應用的原因。
到這里,我們看到要通過Handler向一個線程發送消息要有這幾個步驟:

  • 初始化一個Looper和線程綁定起來,一個線程最多只能有一個Looper(具體原因后面分析)
  • 初始化一個Handler,用來發送消息給Looper的MessageQueue
  • Looper.loop()開啟死循環,不斷輪詢MessageQueue取出消息發送

我們平時常在主線程中new的Handler不用經歷這些步驟,那是應為在app啟動的時候系統幫我們已經做好了。
到這里,有幾個疑問就是:

Handler是怎么和Looper綁定的呢?明明new Handle的時候和Looper半毛錢關系都沒有
多個Handler怎么可以同時向一個Looper的MessageQueue發送消息呢?(ActicityThread中的H和我們平時用的handler就是)


Looper

前面提到的,Handler的使用第一步就是Looper.prepare(),初始化Looper與線程綁定。

public class Looper {
    //全局的sThreadLocal 
    private static final ThreadLocal sThreadLocal = new ThreadLocal();
    // Looper內的消息隊列
    final MessageQueue mQueue;
    // 當前線程
    Thread mThread;
    // 。。。其他屬性

    // 每個Looper對象中有它的消息隊列,和它所屬的線程
    private Looper() {
        mQueue = new MessageQueue();
        mRun = true;
        mThread = Thread.currentThread();
    }

    // 我們調用該方法會在調用線程的TLS中創建Looper對象
    public static final void prepare() {
        //從當前線程拿Looper
        if (sThreadLocal.get() != null) {
           //如果線程中已經初始化了Looper,則拋出異常,這就是為什么一個線程只能有Looper
            throw new RuntimeException("Only one Looper may be created per thread");
        }
         //new 一個Looper存放到線程
        sThreadLocal.set(new Looper());
    }
    // 其他方法
}

prepare()首先調用sThreadLocal.get(),會去檢測當前線程中是否已經初始化了Looper,如果已經有Looper,則拋異常,這里就規定了一個線程只能有一個Looper。如果多次調用Looper.prepare的話就會拋異常。然后調用sThreadLocal.set(new Looper()),將Looper保存到對應的Thread中。發現沒有Looper的構造是private的,所有的Looper實列化都必須通過prepare(),Looper構造里面初始化了MessageQueue對象,所以每個Looper擁有一個消息隊列。
Looper和Thread能夠綁定的關鍵在于sThreadLocal.set(),ThreadLocal是用于線程安全的,具體的不做討論。sThreadLocal是一個靜態變量,系統初始化的時候就存在的。進一步看sThreadLocal.set()的源碼

public void set(T value) {
        //獲取當前線程
        Thread currentThread = Thread.currentThread();
         //拿到Thread中的localValues變量
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        //將全局的ThreadLocal(Looper.sThreadLocal)作為鍵,Looper作為值存到線程的ThreadLocal.Values localValues變量中
        values.put(this, value);
}

Values values(Thread current) {   
       return current.localValues;
}

class Thread{
    ThreadLocal.Values localValues;
}

首先拿到當前線程,獲取Thread的一個變量localValues,Values 是ThreadLocal的一個內部類,以鍵值對存放數據,是一個散列鏈表,和HashMap類似。Looper就是這樣和Thread綁定的。

looper()方法:

public static final void loop() {
        Looper me = myLooper();  //得到當前線程Looper
        MessageQueue queue = me.mQueue;  //得到當前looper的MQ
        // 開始循環
        for (;;) {
            Message msg = queue.next(); // 取出message
            if (msg != null) {
                if (msg.target == null) {
                    // message沒有target為結束信號,退出循環
                    return;
                } 
                // 非常重要!將真正的處理工作交給message的target,即后面要講的handler
                msg.target.dispatchMessage(msg);
               ...
               msg.recycleUnchecked();
            }
        }
    }

myLooper()方法主要是通過sThreadLocal去拿到當前線程的Looper對象
然后拿到消息隊列mQueue

public static Looper myLooper() {  
    return sThreadLocal.get();
}

再通過queue.next()從消息隊列中取出消息msg,然后msg.target.dispatchMessage(msg);把消息發送給handler,msg.target就是handler的引用,是在Handler.sendMessage的時候賦值的,調用handler.dispatchMessage()方法最后會調用我們熟悉的handler.handlerMessage()方法,來讓我們處理消息。
最后一步msg.recycleUnchecked()把消息回收放入消息池內。

小結
Looper.prepare()方法初始化一個Looper對象同時為Looper實例化了一個MessageQueue,并且通過全局變量sThreadLocal將初始化Looper的線程和Looper對象關聯,將Looper對象存儲在當前Thread變量中。
Looper.loop()方法正式將Looper運行起來,不斷循環從消息隊列取消、發送消息、回收消息。

Handler

Handler使用的時候分兩步:
new Handler 、handler.sendMessage()或者handler.post(),這時候Looper影子都沒見著,Handler是怎么把消息發送到線程的Looper.mQueue中去的呢?答案就在Handler的構造中:

 public Handler(Callback callback, boolean async) {
      ...
        //拿到當前線程的Looper
        mLooper = Looper.myLooper();
        //這里表示在初始化Handler之前,必須初始化Looper,不然拋異常
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        //拿到消息列隊
        mQueue = mLooper.mQueue;
        //自定義回調 
        mCallback = callback;
        mAsynchronous = async;
 }

首先在構造里面會去拿到與當前線程(也就是初始化Handler的線程)的Looper、MessageQueue,賦值給Handler的成員變量,這里就解釋了Handler怎么和Looper怎么關聯起來的了而且多個handler可以同時向同一個Looper的消息列隊發送消息,因為他們拿到的是同一個Looper,中間的紐帶就是Thread。
在來看sendMessage(),通過幾個重載的方法最終調用下面函數:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        //直接把關聯looper的MessageQueue 作為自己的MessageQueue ,因此它的消息將發送到關聯looper的MessageQueue 上
        return queue.enqueueMessage(msg, uptimeMillis);
    }

可以看到msg.target = this將當前的Handler賦值給了Message的變量,這就是為什么Looper在取出Message的時候能夠準確的發送給相應的Handler。最終把消息放入消息隊列。uptimeMillis是延時發送的時間,最終在queue.enqueueMessage(msg, uptimeMillis)會存入msg中。
在Handler中的另外幾個點:
1、handler.post(runnable)

public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

最終同樣也是調用sendMessageAtTime(),以前老以為這里Runable是和線程有關,看源碼才知道和線程半毛錢關系都沒有,僅僅只是一個接口,在看getPostMessage(r):

private static Message getPostMessage(Runnable r, Object token) {
        //得到一個消息實例
        Message m = Message.obtain();
        m.obj = token;
       //把Runable對象賦值給了Message的一個變量最終返回msg
        m.callback = r;
        return m;
    }

Runnable 對象賦值給了Message的一個變量,最終還是把返回的Msg,之后流程和handler.sendMessage()一樣的了.
在Looper.loop()中msg.target.dispatchMessage(msg)來發送消息最終調用的是Handler.dispatchMessage()方法,

public void dispatchMessage(Message msg) {
        //如果是handler.post(Runable)走這里
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {//自定義回調
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //常規的回調
            handleMessage(msg);
        }
    }

首先會去檢查msg是否有callback 也就是上面講的Runable對象,如果有則是我們常用的handler.post()這種調用方式,接下來回去判斷mCallback 是否為空,這個mCallback 是Handler的內部類,在Handler的重載的構造中賦值,如果是自定義的回調則調用mCallback.handleMessage(),這樣就不會回調handler.handlerMessage(),最后調用handler.handlerMessage();

到此消息發送、接收的原理和流程就分析完了。這里還有個經典的案列:向子線程中發送消息。
其實Looper的源碼注釋已經寫得很明白了,也給出了示例代碼:

public Handler mHandler;
class LooperThread extends Thread { 
  *      public void run() {
  *          Looper.prepare();
  *
  *          mHandler = new Handler() {
  *              public void handleMessage(Message msg) {
  *                  // process incoming messages here
  *              }
  *          };
  *
  *          Looper.loop();
 }

mHandler.sendMessage();

首先:初始化子線程的Looper,關聯Looper和子線程
其次:初始化Hander,拿到當前線程的Looper賦值給Handler最為成員變量
最后:Looper.loope()死循環,不斷從消息列隊取消息
最終handler.handlerMessage()在Looper.loop()中被調用,所以handlerMessage()運行在子線程中。

未完

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

推薦閱讀更多精彩內容