handler 理解

  • 簡(jiǎn)述

    當(dāng)接觸一個(gè)新事物的時(shí)候我們要去了解它,認(rèn)知過(guò)程一般是三個(gè)為題:

    1. 這個(gè)事物是什么?
    2. 它為什么要這么做?
    3. 它是怎樣做到的纷闺?
  • 是什么?

    handler 就是Google設(shè)計(jì)的一個(gè)線程之間通訊的工具份蝴,實(shí)現(xiàn)線程之間的切換工作犁功。

  • 為什么要這么做?

    這要從Google定制的規(guī)則來(lái)說(shuō)起了婚夫,Google提出浸卦,UI更新一定要在UI線程中實(shí)現(xiàn),這樣做就是提高移動(dòng)端的效率用使用體驗(yàn)案糙。android 的UI線程是采用非安全線程(單線程模型)來(lái)實(shí)現(xiàn)的限嫌,
    這樣保證了UI更新的流暢,提升了用戶體驗(yàn)时捌,但是非安全線程就會(huì)出現(xiàn)多個(gè)線程同時(shí)操作UI的情況怒医,這種情況下UI會(huì)容易出現(xiàn)不可控的錯(cuò)誤,所以Google
    讓所有更新UI的動(dòng)作都在主線程(ActivityThread)奢讨,這樣就不會(huì)出現(xiàn)一些不可控的錯(cuò)誤稚叹,并且在UI線程中不能耗時(shí),耗時(shí)的話就可能阻塞UI線程拿诸,
    UI得不到及時(shí)更新扒袖,出現(xiàn)卡頓,嚴(yán)重的直接出現(xiàn)"ANR"錯(cuò)誤亩码,說(shuō)到底這一切的設(shè)計(jì)都是為了用戶體驗(yàn)季率。

    這樣就出現(xiàn)一個(gè)矛盾點(diǎn):有時(shí)候確實(shí)需要做一些耗時(shí)的動(dòng)作,完成動(dòng)作后還需要更新UI描沟,這些耗時(shí)動(dòng)作只能在子線程中進(jìn)行飒泻,更新UI又需要到UI線程,
    所以為了解決矛盾吏廉,Handler的設(shè)計(jì)就出現(xiàn)了蠢络。

  • 怎么做到的?

    打開(kāi)handler源碼就能看到handler的介紹:


    handler介紹.jpg

    大致意思是:

    Handler是用來(lái)結(jié)合線程的消息隊(duì)列來(lái)發(fā)送、處理“Message對(duì)象”和“Runnable對(duì)象”的工具迟蜜。
    每一個(gè)Handler實(shí)例之后會(huì)關(guān)聯(lián)一個(gè)線程和該線程的消息隊(duì)列。
    當(dāng)你創(chuàng)建一個(gè)Handler的時(shí)候啡省,從這時(shí)開(kāi)始娜睛,它就會(huì)自動(dòng)關(guān)聯(lián)到所在的線程/消息隊(duì)列髓霞,
    然后它就會(huì)陸續(xù)把Message/Runnalbe分發(fā)到消息隊(duì)列,并在它們出隊(duì)的時(shí)候處理掉畦戒。

    Handler有兩個(gè)主要用途:

      1. 推送未來(lái)某個(gè)時(shí)間點(diǎn)將要執(zhí)行的Message或者Runnable到消息隊(duì)列方库。
      2. 在子線程把需要在另一個(gè)線程執(zhí)行的操作加入到消息隊(duì)列中去。
    

    為應(yīng)用程序創(chuàng)建進(jìn)程時(shí)障斋,其主線程專用于運(yùn)行消息隊(duì)列纵潦,
    該隊(duì)列負(fù)責(zé)管理頂級(jí)應(yīng)用程序?qū)ο螅ɑ顒?dòng),廣播接收器等)及其創(chuàng)建的任何窗口垃环。
    您可以創(chuàng)建自己的線程邀层,并通過(guò)Handler與主應(yīng)用程序線程進(jìn)行通信。
    這是通過(guò)調(diào)用與以前相同的post或sendMessage方法完成的遂庄,但是來(lái)自您的新線程寥院。
    然后,將在Handler的消息隊(duì)列中調(diào)度給定的Runnable或Message涛目,并在適當(dāng)時(shí)進(jìn)行處理秸谢。

    • Handler工作原理

handler 主要職責(zé)就是發(fā)送消息與處理消息,一下是發(fā)送消息最終調(diào)用源碼霹肝。

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
           MessageQueue queue = mQueue;
           //判斷MessageQueue是否為空
           if (queue == null) {
               RuntimeException e = new RuntimeException(
                       this + " sendMessageAtTime() called with no mQueue");
               Log.w("Looper", e.getMessage(), e);
               return false;
           }
           return enqueueMessage(queue, msg, uptimeMillis);
       }
   
       private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
            //將 Handler 自身賦值給了 msg.target
           msg.target = this;
           if (mAsynchronous) {
               msg.setAsynchronous(true);
           }
           // 調(diào)用MessageQueue 的 enqueueMessage方法估蹄,將 Message 添加到消息隊(duì)列
           return queue.enqueueMessage(msg, uptimeMillis);
       }

怎么做到的

 public void dispatchMessage(Message msg) {
        //檢查 Message 的 callback 是否為 null
         if (msg.callback != null) {
             //不為 null 直接通過(guò) handleCallback 來(lái)處理消息
             handleCallback(msg);
         } else {
             if (mCallback != null) {
                 if (mCallback.handleMessage(msg)) {
                     return;
                 }
             }
             //callback為空并且mCallback 為空的情況下調(diào)用handleMessage()方法,
             //這個(gè)方法通常就是我們實(shí)現(xiàn)的處理方法
             handleMessage(msg);
         }
     }
  • MessageQueue 消息隊(duì)列工作原理
    MessageQueue 就是一個(gè)裝Message的容器沫换,這是最直接的理解臭蚁,既然是容器,其職責(zé)肯定是添加和移除消息了苗沧。上面介紹handler工作原理的時(shí)候
    提到handler發(fā)送消息的最后是調(diào)用MessageQueue的enqueueMessage方法來(lái)將消息添加到消息隊(duì)列里面的,以下是實(shí)際添加message方法源碼:
boolean enqueueMessage(Message msg, long when) {
           if (msg.target == null) {
               throw new IllegalArgumentException("Message must have a target.");
           }
           if (msg.isInUse()) {
               throw new IllegalStateException(msg + " This message is already in use.");
           }
            //先持有MessageQueue.this鎖
           synchronized (this) {
               if (mQuitting) {
                   IllegalStateException e = new IllegalStateException(
                           msg.target + " sending message to a Handler on a dead thread");
                   Log.w(TAG, e.getMessage(), e);
                   msg.recycle();
                   return false;
               }
   
               msg.markInUse();
               msg.when = when;
               Message p = mMessages;
               boolean needWake;
               
               //如果隊(duì)列為空刊棕,或者當(dāng)前處理的時(shí)間點(diǎn)為0(when的數(shù)值,when表示Message將要執(zhí)行的時(shí)間點(diǎn))待逞,
               //或者當(dāng)前Message需要處理的時(shí)間點(diǎn)先于隊(duì)列中的首節(jié)點(diǎn)甥角,那么就將Message放入隊(duì)列首部
               
               if (p == null || when == 0 || when < p.when) {
                   // New head, wake up the event queue if blocked.
                   msg.next = p;
                   mMessages = msg;
                   needWake = mBlocked;
               } else {
                   // Inserted within the middle of the queue.  Usually we don't have to wake
                   // up the event queue unless there is a barrier at the head of the queue
                   // and the message is the earliest asynchronous message in the queue.
                   needWake = mBlocked && p.target == null && msg.isAsynchronous();
                   Message prev;
                   
                   // 遍歷隊(duì)列中Message,找到when比當(dāng)前Message的when大的Message识樱,
                   //將Message插入到該Message之前嗤无,如果沒(méi)找到則將Message插入到隊(duì)列最后
                   for (;;) {
                       prev = p;
                       p = p.next;
                       if (p == null || when < p.when) {
                           break;
                       }
                       if (needWake && p.isAsynchronous()) {
                           needWake = false;
                       }
                   }
                   msg.next = p; // invariant: p == prev.next
                   prev.next = msg;
               }
   
               // We can assume mPtr != 0 because mQuitting is false.
               //判斷是否需要喚醒,一般是當(dāng)前隊(duì)列為空的情況下怜庸,next那邊會(huì)進(jìn)入睡眠当犯,需要enqueue這邊喚醒next函數(shù)
               if (needWake) {
                   nativeWake(mPtr);
               }
           }
           return true;
   }
  • Looper的工作原理
    Looper 的作用就是不斷的查詢MessageQueue中是否有消息,如果有消息就取出消息給handler處理割疾,如果沒(méi)有消息就阻塞住嚎卫。
    private Looper(boolean quitAllowed) {
            mQueue = new MessageQueue(quitAllowed);
            mThread = Thread.currentThread();
    }

Looper 的構(gòu)造函數(shù)中創(chuàng)建了一個(gè)可MessageQueue 并且將當(dāng)前的線程保存起來(lái)。Looper通過(guò)Looper.loop()開(kāi)啟循環(huán)來(lái)查看是否有消息宏榕。

   /**
    * Run the message queue in this thread. Be sure to call
    * {@link #quit()} to end the loop.
    * 通過(guò)調(diào)用quit() 去結(jié)束loop
    */
    
   public static void loop() {
           final Looper me = myLooper();
           if (me == null) {
               throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
           }
           //...
           final MessageQueue queue = me.mQueue;
           //...
           for (;;) {
               Message msg = queue.next(); // 調(diào)用 MessageQueue 的 next 方法獲取 Message
               if (msg == null) {
                   // No message indicates that the message queue is quitting.
                   // msg == null 表示   MessageQueue 正在退出(調(diào)用了quit等方法)
                   return;
               }
               //....
                try {
                   // msg.target 就是發(fā)送消息的Handler拓诸,因此這里將消息交回 Handler
                   msg.target.dispatchMessage(msg);
                   end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
               } finally {
                   if (traceTag != 0) {
                       Trace.traceEnd(traceTag);
                   }
               }
               //....
           }
       }

注意到loop()中有一個(gè)死循環(huán),死循環(huán)結(jié)束的條件是msg==null侵佃,怎樣讓msg==null呢?調(diào)用MessageQueue的quit方法使msg為空奠支。

是否想過(guò)一個(gè)問(wèn)題馋辈,主線程Looper中運(yùn)行一個(gè)死循環(huán)為什么不會(huì)出現(xiàn)ANR?

首先要明白一點(diǎn)倍谜,Java的線程是允許休眠的迈螟。

Android在主線程中是開(kāi)啟了一個(gè)阻塞式死循環(huán),保證程序一直運(yùn)行尔崔,什么是阻塞式死循環(huán)答毫,簡(jiǎn)單的理解就是:
有事務(wù)就去處理,沒(méi)有的話就休眠您旁。阻塞式循環(huán)的實(shí)現(xiàn)是在Linux中實(shí)現(xiàn)的(epoll和inotify機(jī)制)烙常。ANR的本質(zhì)原因是因?yàn)镸essageQueue中的事件沒(méi)能夠得到及時(shí)的處理,
并不是死循環(huán)取事件造成鹤盒,所以死循環(huán)并不是ANR的必然原因蚕脏。
如果我們自己在主線程寫(xiě)死循環(huán)會(huì)怎樣?答案是侦锯,必然ANR驼鞭,原因是我們的死循環(huán)進(jìn)入了不會(huì)阻塞,不會(huì)釋放cup資源尺碰,后面的事件無(wú)法處理挣棕,Looper的死循環(huán)屬于可阻塞式死循環(huán)。

  • 看完handler源碼我們應(yīng)該很簡(jiǎn)單的回答下面問(wèn)題了亲桥。
  1. 在UI線程中有幾個(gè)Looper對(duì)象洛心?有幾個(gè)MessageQueue對(duì)象?有幾個(gè)Handler對(duì)象题篷?有幾個(gè)Message對(duì)象词身?

    一個(gè)線程中只有一個(gè)Looper對(duì)象,一個(gè)MessageQueue對(duì)象番枚,但是可以有多個(gè)Handler對(duì)象和多個(gè)Message對(duì)象法严。

  2. 怎么保證只有一個(gè)Looper對(duì)象的?

    在handler體系中Looper對(duì)象出現(xiàn)在的地方有三個(gè)葫笼,一是在ActivityThread類中使用Looper.prepareMainLooper()創(chuàng)建Looper對(duì)象深啤,
    二是在hanlder類中使用Looper對(duì)象,三是在Looper的loop方法中使用Looper對(duì)象處理消息路星。而這三個(gè)Looper都是從ThreadLocal中取到溯街,
    ThreadLocal是Looper類的靜態(tài)字段,所以只有一個(gè)ThreadLocal對(duì)象。 prepare方法在UI線程被調(diào)用苫幢,所以只有在Ui線程才能從ThreadLocal對(duì)象中獲取到looper對(duì)象访诱。

  3. 怎么保證只有一個(gè)MessageQueue對(duì)象的?
    首先要明白韩肝,MessageQueue對(duì)象產(chǎn)生的地方,在Looper的構(gòu)造方法中會(huì)產(chǎn)生MessageQueue對(duì)象九榔,而上一題已經(jīng)說(shuō)明一個(gè)線程中只有一個(gè)Looper哀峻,所以對(duì)應(yīng)的MessageQueue也只有一個(gè)。

  4. 為什么發(fā)送消息在子線程哲泊,而處理消息就變成主線程了剩蟀,在哪兒跳轉(zhuǎn)的?

    看源碼我們知道切威,handler發(fā)消息最終是調(diào)用的MessageQueue中的 enqueueMessage 方法育特,這個(gè)方法就將子線程中的消息提交到了MessageQueue中了,而MessageQueue與Looper是依賴關(guān)系的先朦,
    處于同一線程缰冤,這個(gè)線程又是UI線程,所以消息取出的時(shí)候就跳轉(zhuǎn)到UI線程中了喳魏。

  5. looper對(duì)象只有一個(gè)棉浸,在分發(fā)消息時(shí)怎么區(qū)分不同的handler?

    Message類中有個(gè)字段target刺彩,這個(gè)字段是Handler類型的迷郑,Handler的enqueueMessage方法中有這么一句代碼:msg.target = this;即把handler對(duì)象與Message綁定在了一起。
    Looper類的looper方法中分發(fā)消息的代碼是:msg.target.dispatchMessage(msg)创倔。在發(fā)送消息時(shí)handler對(duì)象與Message對(duì)象綁定在了一起嗡害。
    在分發(fā)消息時(shí)首先取出Message對(duì)象,然后就可以得到與它綁定在一起的Handler對(duì)象了畦攘。

  6. 能不能在子線程中創(chuàng)建Handler對(duì)象意敛?

    子線程中是能創(chuàng)建handler的,就像之前提到的handler只是線程間的通訊工具冈在,我們所說(shuō)的UI線程也只是一個(gè)普通的線程涌矢,只是UI線程中已經(jīng)存在Looper對(duì)象,
    在子線程中創(chuàng)建Handler 需要依賴MessageQueue朗徊,因?yàn)閔andler發(fā)送消息是依賴messageQueue中的enqueueMessage首妖,而MessageQueue又是依賴于Looper的,
    所以在子線程中創(chuàng)建Handler只要保證子線程中存在Looper爷恳,從handler構(gòu)造函數(shù)也可以看出如果Looper為空的話是會(huì)直接報(bào)異常的有缆。

  7. 怎么在子線程中得到主線程中handler對(duì)象?

    handler其實(shí)與線程是無(wú)關(guān)的,handler依賴的Looper與線程有關(guān)棚壁,當(dāng)Looper是主線程中的Looper杯矩,handler就會(huì)是主線程中的,handler有一個(gè)構(gòu)造函數(shù)是需要傳入一個(gè)Looper對(duì)象袖外,
    這個(gè)對(duì)象就能確定handler所屬線程史隆。

  8. Handler導(dǎo)致內(nèi)存泄漏的根本原因是什么?
    由上面的講解知道主線程中的Looper曼验,其生命周期應(yīng)該是伴隨整個(gè)應(yīng)用的生命周期的泌射,可以理解為與Application 生命周期一致,在使用handler的時(shí)候鬓照,handler 發(fā)送消息熔酷,其Message是
    是對(duì)handler有引用關(guān)系的 "Handler的enqueueMessage方法中有這么一句代碼:msg.target = this,即把handler對(duì)象與Message綁定在了一起"豺裆,而Message的存放取出與Looper有這緊密關(guān)系
    引用關(guān)系鏈:handler ---->> Message ------>> MessageQueue ----->> Looper ,主線程中Looper 的生命周期是整個(gè)應(yīng)用的生命周期拒秘,如果我們的handler對(duì)activity 有引用,
    那么當(dāng)activity結(jié)束生命周期的時(shí)候(activity應(yīng)該被回收)臭猜,由于Looper 還在執(zhí)行message的相關(guān)操作,導(dǎo)致handler無(wú)法釋放對(duì)activity的引用,最后activity一直無(wú)法釋放出現(xiàn)內(nèi)存泄漏(引用鏈的存在,GC不會(huì)去回收).

最后編輯于
?著作權(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)離奇詭異量愧,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)帅矗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門偎肃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人浑此,你說(shuō)我怎么就攤上這事累颂。” “怎么了凛俱?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵紊馏,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我蒲犬,道長(zhǎng)朱监,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任原叮,我火速辦了婚禮赫编,結(jié)果婚禮上巡蘸,老公的妹妹穿的比我還像新娘。我一直安慰自己擂送,他們只是感情好悦荒,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著嘹吨,像睡著了一般搬味。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上躺苦,一...
    開(kāi)封第一講書(shū)人閱讀 51,754評(píng)論 1 307
  • 那天身腻,我揣著相機(jī)與錄音,去河邊找鬼匹厘。 笑死,一個(gè)胖子當(dāng)著我的面吹牛脐区,可吹牛的內(nèi)容都是我干的愈诚。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼牛隅,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼炕柔!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起媒佣,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤匕累,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后默伍,有當(dāng)?shù)厝嗽跇?shù)林里發(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
  • 文/蒙蒙 一膝擂、第九天 我趴在偏房一處隱蔽的房頂上張望虑啤。 院中可真熱鬧,春花似錦架馋、人聲如沸狞山。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)萍启。三九已至,卻和暖如春屏鳍,著一層夾襖步出監(jiān)牢的瞬間勘纯,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工钓瞭, 沒(méi)想到剛下飛機(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)容