[Android] 消息處理機(jī)制

概述

Android 的消息處理機(jī)制主要是指 Handler 的運(yùn)行機(jī)制以及 Handler 所附帶的 MessageQueue 和 Looper 的工作流程喇伯。

在 Handler 創(chuàng)建完畢之后,就可以通過(guò) Handler.post 方法將一個(gè) Runnable 轉(zhuǎn)換成一個(gè) Message 對(duì)象,它會(huì)調(diào)用 MessageQueue 的 enqueueMessage() 將其放入消息隊(duì)列中:

public final boolean post(Runnable r)
{
   return  sendMessageDelayed(getPostMessage(r), 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);
}

Looper 發(fā)現(xiàn)有新消息到來(lái)時(shí)旁振,就會(huì)處理這個(gè)消息谓罗,最終消息中的 Runnable 或 Handler 的 handleMessage 方法會(huì)被調(diào)用域那。這樣就切換到了創(chuàng)建 Handler 所在的線程中去執(zhí)行了活逆。

其工作流程圖如下所示:


MessageQueue 的工作原理

MessageQueue 類主要包含兩個(gè)操作:插入和讀取,它通過(guò)一個(gè)單鏈表的數(shù)據(jù)結(jié)構(gòu)來(lái)維護(hù)消息列表剪决,在每個(gè) Looper 中都持有一個(gè) MessageQueue 對(duì)象灵汪。

enqueueMessage 方法主要就是往單鏈表中插入一條消息,我們來(lái)看一下 next 讀取方法的代碼:

Message next() {
    // Return here if the message loop has already quit and been disposed.
    // This can happen if the application tries to restart a looper after quit
    // which is not supported.
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        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) {
                    // 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;
            }

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

            // If first time idle, then get the number of idlers to run.
            // Idle handles only run if the queue is empty or if the first message
            // in the queue (possibly a barrier) is due to be handled in the future.
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
                mBlocked = true;
                continue;
            }

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

        // Run the idle handlers.
        // We only ever reach this code block during the first iteration.
        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);
                }
            }
        }

        // Reset the idle handler count to 0 so we do not run them again.
        pendingIdleHandlerCount = 0;

        // While calling an idle handler, a new message could have been delivered
        // so go back and look again for a pending message without waiting.
        nextPollTimeoutMillis = 0;
    }
}

盡管代碼偏長(zhǎng)柑潦,但可以看出 next 方法實(shí)際上是一個(gè)無(wú)限循環(huán)享言,直到 MessageQueue 中有消息則將其取出并刪除,否則會(huì)一直阻塞在這里渗鬼。

Looper 的工作原理

Looper 在消息處理機(jī)制中負(fù)責(zé)不斷從 MessageQueue 中查看是否有新消息览露,如果有的話則進(jìn)行處理,否則就一直阻塞在哪里乍钻。

在初始化 Looper 時(shí)會(huì)創(chuàng)建一個(gè) MessageQueue:

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

當(dāng)調(diào)用 prepare 方法時(shí)肛循,可當(dāng)前線程初始化并綁定一個(gè) Looper,可以看到不能重復(fù)調(diào)用 prepare 方法:

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

最后我們會(huì)調(diào)用 loop 方法來(lái)開啟消息循環(huán)银择,這也是 Looper 中最重要的一個(gè)方法,我們先列出整體代碼累舷,再來(lái)依次進(jìn)行分析:

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;

    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

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

        // This must be in a local variable, in case a UI event sets the logger
        Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        msg.target.dispatchMessage(msg);

        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        // Make sure that during the course of dispatching the
        // identity of the thread wasn't corrupted.
        final long newIdent = Binder.clearCallingIdentity();
        if (ident != newIdent) {
            Log.wtf(TAG, "Thread identity changed from 0x"
                    + Long.toHexString(ident) + " to 0x"
                    + Long.toHexString(newIdent) + " while dispatching to "
                    + msg.target.getClass().getName() + " "
                    + msg.callback + " what=" + msg.what);
        }

        msg.recycleUnchecked();
    }
}

先取出與當(dāng)前線程綁定的 Looper浩考,并取出 Looper 中的 MessageQueue 消息隊(duì)列,接著就進(jìn)入了無(wú)限循環(huán):

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 (;;) {

后面的代碼都是在無(wú)限循環(huán)之內(nèi)被盈,這里取出消息析孽,如果為 null 則退出循環(huán):

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

注意當(dāng) MessageQueue 沒(méi)有消息時(shí),是不會(huì)返回 null 的只怎,只會(huì)一直循環(huán)等待消息袜瞬。只有當(dāng)調(diào)用 quit 方法退出時(shí),才會(huì)返回 null身堡。

public void quit() {
    mQueue.quit(false);
}

接下來(lái)會(huì)對(duì)消息進(jìn)行處理:

msg.target.dispatchMessage(msg);

msg.target 就是發(fā)送該消息的 Handler邓尤,這里就會(huì)將線程切換到該 Handler 所在的線程去處理該 msg,這樣就完成了線程的切換啦~

Handler 的工作原理

在概述中我們已經(jīng)講過(guò)了,Handler.post 方法會(huì)將消息插入到 Looper 中的消息隊(duì)列汞扎,開啟循環(huán)后又會(huì)將該消息轉(zhuǎn)發(fā)到 Handler 所在線程進(jìn)行處理季稳,那么我們就來(lái)看一下 dispatchMessage 方法:

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

msg.callback 是 post 方法中傳遞進(jìn)去的 Runnable 參數(shù),如果不為空澈魄,則:

private static void handleCallback(Message message) {
    message.callback.run();
}

如果為空景鼠,則判斷 Handler 的 Callback 是否為空,這個(gè) Callback 的定義如下:

public interface Callback {
    public boolean handleMessage(Message msg);
}

我們可以用如下這種方式來(lái)創(chuàng)建一個(gè) Handler:
public Handler(Callback callback) {
this(callback, false);
}

實(shí)際上是與我們重寫 Handler.handleMessage 方法差不多的痹扇,只是另一種使用 Handler 的方式而已铛漓。

結(jié)語(yǔ)

那么以上就是,Android 的消息處理機(jī)制了鲫构,這一部分的代碼比較簡(jiǎn)單易懂浓恶,所以推薦大家都去看看。

參考資料

Android中的Handler的具體用法

android的消息處理機(jī)制

《Android 開發(fā)藝術(shù)探索》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末芬迄,一起剝皮案震驚了整個(gè)濱河市问顷,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌禀梳,老刑警劉巖杜窄,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異算途,居然都是意外死亡塞耕,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門嘴瓤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)扫外,“玉大人,你說(shuō)我怎么就攤上這事廓脆∩秆瑁” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵停忿,是天一觀的道長(zhǎng)驾讲。 經(jīng)常有香客問(wèn)我,道長(zhǎng)席赂,這世上最難降的妖魔是什么吮铭? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮颅停,結(jié)果婚禮上谓晌,老公的妹妹穿的比我還像新娘。我一直安慰自己癞揉,他們只是感情好纸肉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布溺欧。 她就那樣靜靜地躺著,像睡著了一般毁靶。 火紅的嫁衣襯著肌膚如雪胧奔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天预吆,我揣著相機(jī)與錄音龙填,去河邊找鬼。 笑死拐叉,一個(gè)胖子當(dāng)著我的面吹牛岩遗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播凤瘦,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼宿礁,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了蔬芥?” 一聲冷哼從身側(cè)響起梆靖,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎笔诵,沒(méi)想到半個(gè)月后返吻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡乎婿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年测僵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谢翎。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡捍靠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出森逮,到底是詐尸還是另有隱情榨婆,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布褒侧,位于F島的核電站纲辽,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏璃搜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一鳞上、第九天 我趴在偏房一處隱蔽的房頂上張望这吻。 院中可真熱鬧,春花似錦篙议、人聲如沸唾糯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)移怯。三九已至香璃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間舟误,已是汗流浹背葡秒。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嵌溢,地道東北人眯牧。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像赖草,于是被迫代替她去往敵國(guó)和親学少。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

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