Android Handler消息機(jī)制源碼分析

概述

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


01.png

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

  • Handler窗声、Looper蘑拯、MessageQueue巧颈、Message的源碼
  • mainThread 的 handler 和 looper(從ActivityThread.main開始)
  • 整個消息發(fā)送的流程
  • 分析每個環(huán)節(jié)的代碼細(xì)節(jié)
  • Handler、Looper和Thread之間的關(guān)系
  • 由handler造成的內(nèi)存泄露問題

整個消息發(fā)送袖扛、接收流程的大致梳理

02.png

第一步:handle.sendMessage()把消息發(fā)送給對應(yīng)線程的Looper的消息列隊(每個looper持有一個MessageQueue)
第二步:Looer.loop()不斷輪詢?nèi)〕鱿?br> 第三步:取出消息后發(fā)送給對應(yīng)的Handler


系統(tǒng)主線程中的Handler

Handler在Android的整個架構(gòu)中間不僅僅只是負(fù)責(zé)更新UI的作用砸泛,通過分析AcitivityThread的源碼可以看到,系統(tǒng)一開始幫我們初始化一個主線程的Handler和Looper,Activity蛆封、Service等的生命周期的消息都是經(jīng)過Handler來處理的唇礁,可見Handler的重要性。我們都知道Java的程序入口是main函數(shù)惨篱,Android自然也是一樣

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

private class H extends Handler {
}

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

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

可以看到Android系統(tǒng)的消息機(jī)制纳令、事件反饋機(jī)制等的消息都是由Handler來進(jìn)行分發(fā)的。
看到這里可能大家會有這么一個疑問克胳,那就是看到AcitivityThread.main方法里面就寥寥幾行代碼平绩,main方法運(yùn)行完了整個App程序不也就退出了嗎?為什么App能夠一直存在呢漠另?因為Looper.loop()里面執(zhí)行了一個死循環(huán)捏雌。循環(huán)退出了,main函數(shù)執(zhí)行完了酗钞,程序也就退出了腹忽,這就是上面Looper.myLooper().quit()退出looper的死循環(huán)就退出應(yīng)用的原因来累。
到這里,我們看到要通過Handler向一個線程發(fā)送消息要有這幾個步驟:

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

我們平時常在主線程中new的Handler不用經(jīng)歷這些步驟着裹,那是應(yīng)為在app啟動的時候系統(tǒng)幫我們已經(jīng)做好了领猾。
到這里,有幾個疑問就是:

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


Looper

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

public class Looper {
    //全局的sThreadLocal 
    private static final ThreadLocal sThreadLocal = new ThreadLocal();
    // Looper內(nèi)的消息隊列
    final MessageQueue mQueue;
    // 當(dāng)前線程
    Thread mThread;
    // 继低。。稍走。其他屬性

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

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

prepare()首先調(diào)用sThreadLocal.get()粱胜,會去檢測當(dāng)前線程中是否已經(jīng)初始化了Looper,如果已經(jīng)有Looper狐树,則拋異常焙压,這里就規(guī)定了一個線程只能有一個Looper。如果多次調(diào)用Looper.prepare的話就會拋異常抑钟。然后調(diào)用sThreadLocal.set(new Looper()),將Looper保存到對應(yīng)的Thread中涯曲。發(fā)現(xiàn)沒有Looper的構(gòu)造是private的,所有的Looper實(shí)列化都必須通過prepare()在塔,Looper構(gòu)造里面初始化了MessageQueue對象掀抹,所以每個Looper擁有一個消息隊列。
Looper和Thread能夠綁定的關(guān)鍵在于sThreadLocal.set()心俗,ThreadLocal是用于線程安全的傲武,具體的不做討論。sThreadLocal是一個靜態(tài)變量城榛,系統(tǒng)初始化的時候就存在的揪利。進(jìn)一步看sThreadLocal.set()的源碼

public void set(T value) {
        //獲取當(dāng)前線程
        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;
}

首先拿到當(dāng)前線程狠持,獲取Thread的一個變量localValues疟位,Values 是ThreadLocal的一個內(nèi)部類,以鍵值對存放數(shù)據(jù)喘垂,是一個散列鏈表甜刻,和HashMap類似绍撞。Looper就是這樣和Thread綁定的。

looper()方法:

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

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

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

再通過queue.next()從消息隊列中取出消息msg祥绞,然后msg.target.dispatchMessage(msg);把消息發(fā)送給handler非洲,msg.target就是handler的引用,是在Handler.sendMessage的時候賦值的蜕径,調(diào)用handler.dispatchMessage()方法最后會調(diào)用我們熟悉的handler.handlerMessage()方法两踏,來讓我們處理消息。
最后一步msg.recycleUnchecked()把消息回收放入消息池內(nèi)兜喻。

小結(jié)
Looper.prepare()方法初始化一個Looper對象同時為Looper實(shí)例化了一個MessageQueue梦染,并且通過全局變量sThreadLocal將初始化Looper的線程和Looper對象關(guān)聯(lián),將Looper對象存儲在當(dāng)前Thread變量中朴皆。
Looper.loop()方法正式將Looper運(yùn)行起來弓坞,不斷循環(huán)從消息隊列取消、發(fā)送消息车荔、回收消息。

Handler

Handler使用的時候分兩步:
new Handler 戚扳、handler.sendMessage()或者h(yuǎn)andler.post()忧便,這時候Looper影子都沒見著,Handler是怎么把消息發(fā)送到線程的Looper.mQueue中去的呢帽借?答案就在Handler的構(gòu)造中:

 public Handler(Callback callback, boolean async) {
      ...
        //拿到當(dāng)前線程的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;
        //自定義回調(diào) 
        mCallback = callback;
        mAsynchronous = async;
 }

首先在構(gòu)造里面會去拿到與當(dāng)前線程(也就是初始化Handler的線程)的Looper砍艾、MessageQueue蒂教,賦值給Handler的成員變量,這里就解釋了Handler怎么和Looper怎么關(guān)聯(lián)起來的了而且多個handler可以同時向同一個Looper的消息列隊發(fā)送消息脆荷,因為他們拿到的是同一個Looper凝垛,中間的紐帶就是Thread。
在來看sendMessage()蜓谋,通過幾個重載的方法最終調(diào)用下面函數(shù):

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

可以看到msg.target = this將當(dāng)前的Handler賦值給了Message的變量,這就是為什么Looper在取出Message的時候能夠準(zhǔn)確的發(fā)送給相應(yīng)的Handler桃焕。最終把消息放入消息隊列剑肯。uptimeMillis是延時發(fā)送的時間,最終在queue.enqueueMessage(msg, uptimeMillis)會存入msg中观堂。
在Handler中的另外幾個點(diǎn):
1让网、handler.post(runnable)

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

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

private static Message getPostMessage(Runnable r, Object token) {
        //得到一個消息實(shí)例
        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)來發(fā)送消息最終調(diào)用的是Handler.dispatchMessage()方法丸凭,

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

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

到此消息發(fā)送汽烦、接收的原理和流程就分析完了。這里還有個經(jīng)典的案列:向子線程中發(fā)送消息莉御。
其實(shí)Looper的源碼注釋已經(jīng)寫得很明白了撇吞,也給出了示例代碼:

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礁叔,關(guān)聯(lián)Looper和子線程
其次:初始化Hander牍颈,拿到當(dāng)前線程的Looper賦值給Handler最為成員變量
最后:Looper.loope()死循環(huán),不斷從消息列隊取消息
最終handler.handlerMessage()在Looper.loop()中被調(diào)用琅关,所以handlerMessage()運(yùn)行在子線程中煮岁。

未完

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市涣易,隨后出現(xiàn)的幾起案子画机,更是在濱河造成了極大的恐慌,老刑警劉巖新症,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件步氏,死亡現(xiàn)場離奇詭異,居然都是意外死亡徒爹,警方通過查閱死者的電腦和手機(jī)荚醒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來隆嗅,“玉大人腌且,你說我怎么就攤上這事¢晃停” “怎么了铺董?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我精续,道長坝锰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任重付,我火速辦了婚禮顷级,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘确垫。我一直安慰自己弓颈,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布删掀。 她就那樣靜靜地躺著翔冀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪披泪。 梳的紋絲不亂的頭發(fā)上纤子,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天,我揣著相機(jī)與錄音款票,去河邊找鬼控硼。 笑死,一個胖子當(dāng)著我的面吹牛艾少,可吹牛的內(nèi)容都是我干的卡乾。 我是一名探鬼主播,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼缚够,長吁一口氣:“原來是場噩夢啊……” “哼幔妨!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起潮瓶,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎钙姊,沒想到半個月后毯辅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡煞额,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年思恐,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片膊毁。...
    茶點(diǎn)故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡胀莹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出婚温,到底是詐尸還是另有隱情描焰,我是刑警寧澤,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站荆秦,受9級特大地震影響篱竭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜步绸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一掺逼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瓤介,春花似錦吕喘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至漾月,卻和暖如春病梢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背梁肿。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吩蔑,地道東北人钮热。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓烛芬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親赘娄。 傳聞我的和親對象是個殘疾皇子仆潮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評論 2 359

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