Handler源碼閱讀

Handler源碼的閱讀主要圍繞Lopper這個對象和這個對象中的Message隊列這兩個東西子刮。

Message

在Android的Handler中窑睁,會通過在子線程發(fā)送Message消息回到主線程并將數(shù)據(jù)更新到主線程的UI葵孤。而這個過程首先從sendMessage這個方法入手沙郭。

  public final boolean sendMessage(@NonNull Message msg) {
          return sendMessageDelayed(msg, 0);
  }

這里會調用 sendMessageDelayed 方法病线,這個方法會帶有一個延時發(fā)送的性質鲤嫡。

   public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
   }

繼續(xù)跟蹤

  public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        //Handler 的消息隊列
        MessageQueue queue = mQueue;
        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);
    }

跟蹤到 sendMessageAtTime 這個方法可以看到 enqueueMessage 這個方法并返回一個布爾值暖眼。

進入到這個方法里

 private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        //將Handler自身放入Message對象中
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
  }

這里調用了queue的enqueueMessage方法诫肠,繼續(xù)追蹤。

boolean enqueueMessage(Message msg, long when) {
        //這里的target是發(fā)送消息Handler自身
        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.");
        }

        synchronized (this) {
         
            //部分代碼省略...

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            //將消息放入隊列之中,并進行排序
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                //當隊列沒有數(shù)據(jù)蛤铜,將消息放入隊列鏈表頭部
                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;
                for (;;) {
                    prev = p;
                    p = p.next;
                    //判斷消息優(yōu)先級
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                //將消息插入鏈表中围肥,并將next指向下一個message
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

           //部分代碼省略...

        }
        return true;
    }

可以看到穆刻,這里主要就是將Message對象放入到隊列之中蛹批,而之前那個延時發(fā)送篮愉,在這里就會通過判斷 when 來插入到隊列之中试躏,改變消息隊列發(fā)送順序颠蕴。這也是為什么隊列是一個鏈表結構的原因助析。

Looper

上面的 Message 隊列只看到了將消息放入隊列之中外冀,并沒有看到 handlerMessage() 方法的調用雪隧,而 handlerMessage 則跟Lopper有關脑沿。

之前在項目中马僻,用到 Handler 時韭邓,有想到過能不能通過 Handler 方式來讓子線程發(fā)送消息給主線程女淑。而研究的結果是可以的诗力,但是要調用 Looper.prepareLooper() 和 Looper.loop()這兩個方法凰浮。而這兩個方法就是Handler發(fā)送消息回調handlerMessage()的關鍵。

在 ActvitiyThread 類的main()方法中瓣窄,會調用 Looper.prepareMainLooper()Looper.loop() 這兩個方法俺夕。

 public static void main(String[] args) {

        //...代碼省略      

        //生成主線程的looper
        Looper.prepareMainLooper();

        //...代碼省略     
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

Looper.prepareMainLooper()

先追蹤Looper.prepareMainLooper()這個方法

public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

上面這段代碼可以看到調用了 prepare(false)myLooper() 來那個個方法姨谷,下面那個方法看名字就能知道是獲取了 Looper 對象梦湘。

先看prepare(false) 這個方法捌议。

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //生成looper對象哼拔,并放入主線程的ThreadLocal對象中
        //此處的set方法就是ThreadLocal獲取當前線程并保存和將對象保存到ThreadLocal中的方式
        //set方法中會通過當前線程去ThreadLocalMap中拿到線程的數(shù)據(jù)信息
        sThreadLocal.set(new Looper(quitAllowed));
    }

可以看到 sThreadLocal.set(new Looper(quitAllowed)) 這段,這段代碼做了兩件事情:

  1. 生成 Looper 對象瓣颅,并放入主線程的 ThreadLocal 對象中

  2. ThreadLocal獲取當前線程并將線程和 Lopper 對象保存到 ThreadLocalMap 中

這里的set方法倦逐,會將生成的 Looper 對象放入到 ThreadLocalMap 中,這個Map的Key為當前的線程對象宫补,Value 為 Lopper 對象檬姥。

而myLooper()方法就是從ThreadLocalMap中獲取Looper對象。

   public static @Nullable Looper myLooper() {
        //通過ThreadLocal獲取到當前線程的looper對象
        //如果是主線程守谓,則在ActivityThread的main()方法中已經聲明了looper對象的生成
        return sThreadLocal.get();
    }

Looper.loop();

接下來看 Looper.loop() 方法穿铆。

 public static void loop() {
        //拿到當前線程的looper對象
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        //獲取looper對象中保存的隊列
        final MessageQueue queue = me.mQueue;

       //...省略代碼

        //無線循環(huán)隊列
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            //...省略代碼
          
            try {
                //調用Handler的dispatchMessage方法
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
          
            //...省略代碼

            //回收此次的Message對象,之后申請Message對象時可以重復使用
            msg.recycleUnchecked();
        }
    }

而上面這段代碼就能看到通過 myLooper() 方法拿到 Looper 對象您单,并且通過 Looper 對象拿到隊列斋荞。然后無線循環(huán)從這個對象拿出消息虐秦,最后調用 Handler 的 dispatchMessage(msg) 方法。

這個在主線程無線循環(huán)不會卡死手機的原因就在它是在 ActvitiyThread 類的main()方法中幸逆,并且是最后執(zhí)行的一個方法。這樣其實也可以理解為正因為這個無線循環(huán),我們的APP才會一直執(zhí)行,直到用戶關閉APP踏揣。

 /**
     * Handle system messages here.
     */
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

而dispatchMessage方法里就會調用handleMessage括享,也就是回調Handler handleMessage方法剩愧。

這樣犬第,Handler整個流程就走完了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖载慈,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件秤茅,死亡現(xiàn)場離奇詭異课幕,居然都是意外死亡,警方通過查閱死者的電腦和手機润绎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人巡李,你說我怎么就攤上這事辐宾〕ǜ穑” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵卿叽,是天一觀的道長。 經常有香客問我,道長屁使,這世上最難降的妖魔是什么酬蹋? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任虐呻,我火速辦了婚禮忽孽,結果婚禮上瘾腰,老公的妹妹穿的比我還像新娘栖雾。我一直安慰自己竞慢,他們只是感情好败潦,可當我...
    茶點故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布闷板。 她就那樣靜靜地躺著,像睡著了一般其兴。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天,我揣著相機與錄音常柄,去河邊找鬼相种。 笑死衬潦,一個胖子當著我的面吹牛,可吹牛的內容都是我干的耻瑟。 我是一名探鬼主播低淡,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼戒傻,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了津坑?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體郭怪,經...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡栽渴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了佛致。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片罐脊。...
    茶點故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖矛市,靈堂內的尸體忽然破棺而出午阵,到底是詐尸還是另有隱情,我是刑警寧澤惧眠,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布籽懦,位于F島的核電站,受9級特大地震影響氛魁,放射性物質發(fā)生泄漏暮顺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一秀存、第九天 我趴在偏房一處隱蔽的房頂上張望捶码。 院中可真熱鬧,春花似錦或链、人聲如沸惫恼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽祈纯。三九已至,卻和暖如春叼耙,著一層夾襖步出監(jiān)牢的瞬間腕窥,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工筛婉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留油昂,地道東北人。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓倾贰,卻偏偏與公主長得像冕碟,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子匆浙,可洞房花燭夜當晚...
    茶點故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內容

  • 首先handler的分發(fā)過程主要涉及到四個類:Handler(分發(fā)器),Message(消息),Looper(輪詢...
    lucasDev閱讀 210評論 0 0
  • handler工作流程圖 參考DonKingLiang的文章圖片老是上傳失敗安寺,不知道為啥,本地和網(wǎng)絡的都不可以首尼。挑庶。...
    etrnel閱讀 251評論 0 0
  • Handler是Android中最常用線程通訊方式之一、也是非UI線程與線程通訊的主要方式软能。 你可能有個疑問基礎a...
    CrazyDevp閱讀 148評論 0 0
  • 一迎捺、提出問題 面試時常被問到的問題: 簡述 Android 消息機制 Android 中 Handler,Loop...
    崽子豬閱讀 1,521評論 0 10
  • 每日一題: Handler源碼 深入了解handler 面試率: ★★★☆☆ 面試技巧與建議 handler作為A...
    林銳波閱讀 525評論 0 1