Android Handler消息機制及消息類型(同步消息、異步消息趟庄、消息屏障)

Android 消息機制作為系統(tǒng)運行的機制之一括细,在大一點的廠子面試被問到的概率比較大,可見它的重要性戚啥。下面將分兩部分奋单,首先介紹消息的整體機制,接著聊聊消息的類型猫十。

一览濒、消息機制

在消息機制中,有下面幾個角色:

  • a. Message: 消息實體
  • b. MessageQueue: 消息隊列拖云,存放Message贷笛,以鏈表的方式實現(xiàn)
  • c. Looper: 對MessageQueue進行循環(huán),獲取Message給Handler處理
  • d. Handler: 對Message進行處理
    下面從源碼的角度分析它們之間是怎么協(xié)作的
Looper:
public final class Looper {
    ...

    /** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

    /**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    ...
}

從上述源碼可知宙项,Looper提供了兩個方法來創(chuàng)建Looper對象乏苦,并將創(chuàng)建的對象保存在sThreadLocal。
從prepare方法可以看到尤筐,每個線程只允許創(chuàng)建一個Looper對象邑贴,否則會拋異常。
而我們在主線程創(chuàng)建Handler時叔磷,則不用自己創(chuàng)建Looper拢驾,那主線程的Looper是在哪里被創(chuàng)建的呢?我們看下prepareMainLooper()方法的注釋
在 Looper 的 prepareMainLooper() 方法注釋中可以看到這樣一句話:

Initialize the current thread as a looper, marking it as an application's main looper.
The main looper for your application is created by the Android environment, so 
you should never need to call this function yourself.  See also: {@link #prepare()}

意思是說:將當前線程初始化為looper改基,將其標記為應用程序的主循環(huán)繁疤。您的應用程序的主要循環(huán)器是由Android環(huán)境創(chuàng)建的,永遠不應該自己調用這個函數(shù)秕狰。
由此可知稠腊,是系統(tǒng)運行時就幫我們創(chuàng)建了Looper了,可以看下ActivityThread的main方法:

public static void main(String[] args) {
    ...
    Looper.prepareMainLooper();
    ...
    Looper.loop();
}

印證了上述的注釋鸣哀。
接下來看下里面loop()方法:

    /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
        ...
        for (;;) {
            // 從消息隊列中取得消息架忌,具體的在下面MessageQueue進行解析
            Message msg = queue.next(); 
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ...
            try {
                // target是Handler,由Handler進行處理我衬,具體看下面Handler的解析
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            ...
        }
    }
Handler:

首先是send系列方法叹放,拿一個出來看:

    public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }
    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    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);
    }

可見最后是調用了sendMessageAtTime方法饰恕,其它的方法包括post系列的方法也一樣,最終都是調用了sendMessageAtTime井仰,不同的是最后傳入的uptimeMillis埋嵌。
然后再看下Looper調用Handler的dispatchMessage方法

    /**
     * Handle system messages here.
     * 處理消息
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
MessageQueue:

接下來看下sendMessageAtTime里的enqueueMessage方法,此方法為將一個消息入隊俱恶。進入MessageQueue類

    // msg-上面說的消息實體雹嗦, when-需要延時執(zhí)行的時長
    boolean enqueueMessage(Message msg, long when) {
        // 參數(shù)檢查省略
        synchronized (this) {
            // 檢查Looper及相應的隊列是否已經終止,是的話對msg進行回收并退出
            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;  // mMessages指向隊列頭部
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                // 假設隊列為空 或 msg不需要延時 或 
                // msg需要延時的時長小于隊頭消息的延時時長合是,
                // 則將msg插入對頭
                msg.next = p;
                mMessages = msg;
                // 標記是否需要喚醒了罪,mBlocked表示現(xiàn)在消息隊列是否處于阻塞狀態(tài)
                // 阻塞的原因在下面next()方法獲取消息時再進行說明
                needWake = mBlocked; 
            } else {
                // 將msg插入在隊列的中部,插入的位置
                // 根據(jù)when得到聪全,when小的msg會在前面
                // 這樣方便保證之后先出隊msg都是需要
                // 先執(zhí)行的捶惜,從而保證在delay的時候過后執(zhí)行msg
                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;
            }
            // 當在next()獲取msg時,假設獲取到的when大于0荔烧,
            // 說明此時需要延時when后才能
            // 執(zhí)行這個msg吱七,因此進行了阻塞鹤竭,mBlocked=true踊餐。
            // 但是這個時候有新的消息入隊并處于隊頭位置,
            // 因此先于上一個隊頭消息執(zhí)行臀稚,所醒
            // 以此時需要喚醒隊列吝岭,才能保證后來者需要先執(zhí)行
            // 的不會因為阻塞而中斷
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

接下來看看上面提到的出隊的方法next():

    Message next() {
        // 條件檢查,省略
        // 第一次循環(huán)的時候為-1吧寺,其它情況不會時-1
        int pendingIdleHandlerCount = -1; 
        int nextPollTimeoutMillis = 0; // 需要進行阻塞的時間
        for (;;) { // 死循環(huán)窜管,不斷獲取msg
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            // 當頭部消息還未到執(zhí)行時間時,
            // 調用本地方法進行阻塞掛起
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) { // 判斷是否有消息屏障
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    // 有消息屏障的話稚机,取出后面第一條異步消息
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // 下一個消息還未到執(zhí)行時間幕帆,因此設置一個時間進行阻塞,
                        // 過了這個時間赖条,將喚醒
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 此時有消息到執(zhí)行時間了失乾,則設置隊列處于不阻塞狀態(tài),
                        // 將隊頭出隊纬乍,并返回
                        mBlocked = false; 
                        if (prevMsg != null) { // 有消息屏障的情況下碱茁,將鏈表的前部分的同步消息連接到后面
                            prevMsg.next = msg.next;
                        } else { // 否則,直接將mMessages指向下一個消息即可
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // 如果沒有消息仿贬,則設置阻塞時長為無限纽竣,直到被喚醒
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // 第一次循環(huán) 且 (消息隊列為空 或 
                // 消息隊列的第一個消息的觸發(fā)時間還沒有到)時,
                // 表示處于空閑狀態(tài)
                // 獲取到 IdleHandler 數(shù)量
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // 沒有 IdleHandler 需要運行,循環(huán)并等待
                    mBlocked = true; // 設置阻塞狀態(tài)為 true
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // 運行 IdleHandler蜓氨,只有第一次循環(huán)時才會運行
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // 重置 IdleHandler 的數(shù)量為 0聋袋,確保不會重復運行
            // pendingIdleHandlerCount 置為 0 后,上面可以通
            // 過 pendingIdleHandlerCount < 0 判斷是否是第一次循環(huán)语盈,
            // 不是第一次循環(huán)則 pendingIdleHandlerCount 的值不會變舱馅,始終為 0缰泡。
            pendingIdleHandlerCount = 0;

            // 在執(zhí)行 IdleHandler 后刀荒,可能有新的消息插
            // 入或消息隊列中的消息到了觸發(fā)時間,
            // 所以將 nextPollTimeoutMillis 置為 0棘钞,表示不
            // 需要阻塞缠借,重新檢查消息隊列。
            nextPollTimeoutMillis = 0;
        }
    }
經過上面兩個方法宜猜,這里總結下消息機制的總體流程:

消息在入隊(即有新消息加入)的時候泼返,會根據(jù)delay的時間,在隊列找到合適的位置入隊姨拥,從而保證整個隊列的順序是以延遲時間從小到大排序绅喉。

  • a. 當入隊的消息的delay時間比原先隊頭消息短的時候,或者隊列為空的時候叫乌,則消息會入隊在隊頭柴罐,并且當此時列表處于阻塞狀態(tài)時,則喚醒隊列憨奸;
  • b. 否則入隊的位置為非隊頭革屠。

這個特性方便后續(xù)獲取消息即出隊的時候,直接出隊頭消息排宰,即是最優(yōu)先需要執(zhí)行的消息似芝。出隊時

  • a. 若隊列為空,則無限長時間進行阻塞板甘;
  • b. 出隊的消息要是到達執(zhí)行時間了党瓮,則出隊;
  • c. 出隊的消息還沒到執(zhí)行時間盐类,則進行對應時間的阻塞麻诀。

二、消息類型(同步消息傲醉、異步消息蝇闭、消息屏障)

上面已經介紹完了Handler的機制,代碼注釋中提及了消息屏障硬毕、異步消息呻引,下面具體介紹下這部分內容。

Hander的消息類型有三種吐咳,分別是同步消息逻悠、異步消息元践、消息屏障,下面先看看使用方式童谒。

1. 同步消息

我們平常使用Handler發(fā)送的消息基本都是同步消息单旁,例如下面的代碼:

        Handler handler = new Handler(Looper.getMainLooper());

        // 使用方式1
        handler.post(new Runnable() {
            @Override
            public void run() {
                // do something
            }
        });

        // 使用方式2
        handler.sendEmptyMessage(0);

        // 使用方式3
        Message msg = Message.obtain();
        msg.what = 1;
        handler.sendMessage(msg);

        ...
2. 異步消息

使用方式如下:

        Message msg = Message.obtain();
        msg.what = 2;
        msg.setAsynchronous(true); // 設置消息為異步消息
        handler.sendMessage(msg);
3. 消息屏障

添加消息屏障的API如下:

    /**
     * Posts a synchronization barrier to the Looper's message queue.
     *
     * @return A token that uniquely identifies the barrier.  This token must be
     * @hide
     */
    public int postSyncBarrier() {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }

該方法是@hide的,因此需要自行反射調用饥伊,添加完消息屏障后象浑,會返回這個消息的token,移除消息屏障時琅豆,需要用到這個token愉豺,具體API如下(同樣需要反射調用):

    /**
     * Removes a synchronization barrier.
     *
     * @param token The synchronization barrier token that was returned by
     * {@link #postSyncBarrier}.
     *
     * @throws IllegalStateException if the barrier was not found.
     *
     * @hide
     */
    public void removeSyncBarrier(int token) {
    }

上面介紹了三種消息類型的大體使用方式,那它們有什么作用茫因,或者有什么區(qū)別呢蚪拦?

下面回到MessageQueue#next()的源碼中:

    Message next() {
        for (;;) {
            synchronized (this) {
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) { // 判斷是否有消息屏障
                    // 有消息屏障的話,取出后面第一條異步消息
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                    } else {
                        if (prevMsg != null) { // 有消息屏障的情況下冻押,將鏈表的前部分的同步消息連接到后面
                            prevMsg.next = msg.next;
                        } else { // 否則驰贷,直接將mMessages指向下一個消息即可
                            mMessages = msg.next;
                        }
                    }
                }
            }
        }
    }

從上面的代碼可以看出,消息屏障的作用是來阻塞消息隊列后面的同步消息洛巢,而異步消息不受消息屏障影響括袒。在無消息屏障的情況下,同步消息與異步消息無本質上的區(qū)別狼渊。

轉載請注明出處:http://www.reibang.com/p/9ecccd6d4506

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末箱熬,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子狈邑,更是在濱河造成了極大的恐慌城须,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件米苹,死亡現(xiàn)場離奇詭異糕伐,居然都是意外死亡,警方通過查閱死者的電腦和手機蘸嘶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門良瞧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人训唱,你說我怎么就攤上這事褥蚯。” “怎么了况增?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵赞庶,是天一觀的道長。 經常有香客問我,道長歧强,這世上最難降的妖魔是什么澜薄? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮摊册,結果婚禮上肤京,老公的妹妹穿的比我還像新娘。我一直安慰自己茅特,他們只是感情好忘分,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著温治,像睡著了一般饭庞。 火紅的嫁衣襯著肌膚如雪戒悠。 梳的紋絲不亂的頭發(fā)上熬荆,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機與錄音绸狐,去河邊找鬼卤恳。 笑死,一個胖子當著我的面吹牛寒矿,可吹牛的內容都是我干的突琳。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼符相,長吁一口氣:“原來是場噩夢啊……” “哼拆融!你這毒婦竟也來了?” 一聲冷哼從身側響起啊终,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤镜豹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蓝牲,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體趟脂,經...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年例衍,在試婚紗的時候發(fā)現(xiàn)自己被綠了昔期。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡佛玄,死狀恐怖硼一,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情梦抢,我是刑警寧澤般贼,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響具伍,放射性物質發(fā)生泄漏翅雏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一人芽、第九天 我趴在偏房一處隱蔽的房頂上張望望几。 院中可真熱鬧,春花似錦萤厅、人聲如沸橄抹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽楼誓。三九已至,卻和暖如春名挥,著一層夾襖步出監(jiān)牢的瞬間疟羹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工禀倔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留榄融,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓救湖,卻偏偏與公主長得像愧杯,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子鞋既,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345