Android Handler postDelayed的原理

前言

我們經(jīng)常用Handler中的postDelayed方法進行延遲操作溢吻,像這樣

new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                //操作內(nèi)容
            }
        },100);

我們都知道Handler的機制是將消息通過sendMessage()放入到MessageQueen中 然后looper輪訓(xùn)器通過輪訓(xùn)取出MessageQueen中的消息回調(diào)目標(biāo)Handler中的handlerMessage()。

可是postDelayed(),這個方法他的底層是怎么做的呢停撞?是延遲把消息放入到MessageQueen的嗎,點進去看看,發(fā)現(xiàn)不是

一.源碼分析

1.點進去看postDelayed()中的方法。里面調(diào)用sendMessageDelayed方法备蚓,和post() 里面調(diào)用的方法一樣。

   public final boolean postDelayed(Runnable r, long delayMillis)
    {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }
    

2.我們再點進去看下sendMessageDelayed()方法,

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

里面調(diào)用了sendMessageAtTime()囱稽,這里的SystemClock.uptimeMillis()是獲取系統(tǒng)從開機啟動到現(xiàn)在的時間,期間不包括休眠的時間,這里獲得到的時間是一個相對的時間,而不是通過獲取當(dāng)前的時間(絕對時間)郊尝。

而之所以使用這種方式來計算時間,而不是獲得當(dāng)前currenttime來計算粗悯,在于handler會受到阻塞虚循,掛起狀態(tài),睡眠等样傍,這些時候是不應(yīng)該執(zhí)行的横缔;如果使用絕對時間的話,就會搶占資源來執(zhí)行當(dāng)前handler的內(nèi)容衫哥,顯然這是不應(yīng)該出現(xiàn)的情況茎刚,所以要避免。

3.點進sendMessageAtTime()方法看看

    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        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);
    }

追到這里依然沒有看到撤逢,他在存放的時候有什么不同膛锭,但是顯然證實了消息不是延遲放進MessageQueen的,那是腫么處理的蚊荣,是在輪訓(xùn)的時候處理的嗎初狰?,

4.我們點進Looper中看一下互例,主要代碼奢入,Looper中的looper調(diào)用了MessageQueen中的next方法,難道是在next()方法中處理的媳叨?

 public static void loop() {
···

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
      ···

5.我們點進MessageQueen中的next()方法

  for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);
    ···
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

           
               
        ···
        }
}

很貼心的給出了注釋解釋“ Next message is not ready. Set a timeout to wake up when it is ready.”腥光,翻譯“下一條消息尚未準(zhǔn)備好关顷。設(shè)置一個超時,以便在準(zhǔn)備就緒時喚醒武福∫樗”

when就是uptimeMillis, for (;;) 相當(dāng)于while(true)捉片,如果頭部的這個Message是有延遲而且延遲時間沒到的(now < msg.when)平痰,不返回message 而且會計算一下時間(保存為變量nextPollTimeoutMillis),然后再循環(huán)的時候判斷如果這個Message有延遲伍纫,就調(diào)用nativePollOnce(ptr, nextPollTimeoutMillis)進行阻塞觉增。nativePollOnce()的作用類似與object.wait()。得出結(jié)論是通過阻塞實現(xiàn)的翻斟。

6.但是如果在阻塞這段時間里有無延遲message又加入MessageQueen中又是怎么實現(xiàn)立即處理這個message的呢?说铃,我們看MessageQueen中放入消息enqueueMessage()方法

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

        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;
            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;
                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.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

在這里p 是現(xiàn)在消息隊列中的頭部消息访惜,我們看到| when < p.when 的時候它交換了放入message與原來消息隊列頭部P的位置,并且 needWake = mBlocked; (在next()中當(dāng)消息為延遲消息的時候mBlocked=true)腻扇,繼續(xù)向下看 當(dāng)needWake =true的時候nativeWake(mPtr)(喚起線程)

一切都解釋的通了债热,如果當(dāng)前插入的消息不是延遲message,或比當(dāng)前的延遲短幼苛,這個消息就會插入頭部并且喚起線程來

二.整理

我們把我們跟蹤的所有信息整理下

1.消息是通過MessageQueen中的enqueueMessage()方法加入消息隊列中的窒篱,并且它在放入中就進行好排序,鏈表頭的延遲時間小舶沿,尾部延遲時間最大

2.Looper.loop()通過MessageQueue中的next()去取消息

3.next()中如果當(dāng)前鏈表頭部消息是延遲消息墙杯,則根據(jù)延遲時間進行消息隊列會阻塞,不返回給Looper message括荡,知道時間到了高镐,返回給message

4.如果在阻塞中有新的消息插入到鏈表頭部則喚醒線程

5.Looper將新消息交給回調(diào)給handler中的handleMessage后,繼續(xù)調(diào)用MessageQueen的next()方法畸冲,如果剛剛的延遲消息還是時間未到嫉髓,則計算時間繼續(xù)阻塞

三總結(jié)

handler.postDelay() 的實現(xiàn) 是通過MessageQueue中執(zhí)行時間順序排列,消息隊列阻塞邑闲,和喚醒的方式結(jié)合實現(xiàn)的算行。

如果真的是通過延遲將消息放入到MessageQueen中,那放入多個延遲消息就要維護多個定時器苫耸,

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末州邢,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子鲸阔,更是在濱河造成了極大的恐慌偷霉,老刑警劉巖迄委,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異类少,居然都是意外死亡叙身,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門硫狞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來信轿,“玉大人,你說我怎么就攤上這事残吩〔坪觯” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵泣侮,是天一觀的道長即彪。 經(jīng)常有香客問我,道長活尊,這世上最難降的妖魔是什么隶校? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮蛹锰,結(jié)果婚禮上深胳,老公的妹妹穿的比我還像新娘。我一直安慰自己铜犬,他們只是感情好舞终,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著癣猾,像睡著了一般敛劝。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上纷宇,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天攘蔽,我揣著相機與錄音,去河邊找鬼呐粘。 笑死满俗,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的作岖。 我是一名探鬼主播唆垃,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼痘儡!你這毒婦竟也來了辕万?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎渐尿,沒想到半個月后醉途,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡砖茸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年隘擎,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凉夯。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡货葬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出劲够,到底是詐尸還是另有隱情震桶,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布征绎,位于F島的核電站蹲姐,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏人柿。R本人自食惡果不足惜淤堵,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望顷扩。 院中可真熱鬧,春花似錦慰毅、人聲如沸隘截。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽婶芭。三九已至,卻和暖如春着饥,著一層夾襖步出監(jiān)牢的瞬間犀农,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工宰掉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留呵哨,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓轨奄,卻偏偏與公主長得像孟害,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子挪拟,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

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