Android的消息機制分析

1、前言

衆所周知,Android系統是以消息來驅動的系統,說到消息,咱們會立馬想到Handler、MessageQueue和Looper,事實上,android中的消息機制就和這三者緊密聯繫的,這三者是相互關聯,誰都離不開誰。接下來咱們就講一下消息機制,以及須要重點注意的幾點內容。android

2、簡述應用程序的啓動

在開始講消息以前,咱們有必要說說應用程序的啓動,由於啓動的時候幹了不少和消息相關的事,有個大體的印象。以下:程序員

  • 應用程序的入口是在ActivityThread的main()函數,初始化mainLooper,而後建立消息循環隊列。
  • 而後去建立一個ActivityThread,在初始化的時候會去建立內部Handler H 和用於消息通訊的ApplicationThread,也就是binder。
  • Binder收到遠程AMS傳遞過來的消息,Handler會將消息enqueu到消息隊列中去,UI線程從消息隊列中取到消息,開始loop。

後面的就是view和window的操做的,這裏省略不講,看到了吧,一切皆消息面試

3、消息機制分析

好了,接下來就進入正題,咱們先理解一個問題。爲啥非UI線程不支持頁面的刷新,爲啥用handler就能夠。算法

3.一、爲啥非UI線程不能刷新界面

由於Android中的UI控件不是線程安全的,若是併發的去更新UI會形成不可預期的後果,那麼爲啥不像線程同樣用加鎖機制呢,兩點:shell

  • 複雜度問題:對控件加鎖,ui邏輯將變得很複雜,由於咱們須要去管理這個鎖。
  • 效率問題:鎖機制將會下降ui的訪問效率,由於鎖都是會阻塞線程的執行。

鑑於以上的缺點,採用了單線程來處理UI操做,動態去切換handler就ok了。安全

3.二、消息機制的總體框架

下面是三者之間工做流程bash

  • 在thread1中,Handler發送消息到thread2的MQ,實質上就是enqueueMessage到MQ,這裏有post和send方法,post的底層也是用send來實現的。
  • MQ一直處於監聽狀態,當有消息到來的時候,會調用handler中的looper,注意這個looper是運行在thread1裏面。
  • Looper開始分發消息,因爲Looper在Handler線程中,因此Handler的業務邏輯就切換到建立Handler所在的線程中去執行了。

小結一下:因此想象一下ui線程更新ui的場景,咱們在ui線程中建立Handler,當在子線程中處理完耗時操做的時候須要更新ui,這時候就交給handler去loop,實質上就是交給建立handler的ui線程,這樣咱們更新ui就是在主線程了,這就好理解了。數據結構

3.三、MessageQueue的工做原理

消息隊列,從字面上看是隊列,不過它實質上就是單鏈表的數據結構,由於MQ會進行大量的刪除和插入操做,單鏈表在這一塊的效率頗有優點。咱們主要看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; } 複製代碼

這裏的操做實質上就是單鏈表的插入操做。接下來看看next方法app

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;
        }
    }
複製代碼

這裏是讀取消息的操做,它是一個無限循環。若是消息隊列中有消息,next方法會返回這條消息,而且從單鏈表中刪除這條消息,當沒有消息的時候消息隊列會一直處於等待狀態。

小結: 所謂的消息發送和讀取,歸根到底就是單鏈表的刪除和插入。因此同志們,這下知道理解和掌握經常使用數據結構的重要性了吧,即使強如google程序員也須要寫如此簡單的算法滴。

3.四、Looper的工做原理

咱們知道Handler的工做離不開Looper,沒有Looper會報錯,以下:

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;
複製代碼

相信開發這都遇到過上面的異常吧,就是在只有Handler沒有Looper。那麼怎樣在Handler中初始化looper呢,其實很是簡單:

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));
    }
複製代碼

執行一下prepare方法就能夠了,這裏面new了一個Looper,這就是爲該Handler所在的線程生成一個Looper,接下來就是開始消息循環了。

/**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the 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;

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

        // Allow overriding a threshold with a system prop. e.g.
        // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
        final int thresholdOverride =
                SystemProperties.getInt("log.looper."
                        + Process.myUid() + "."
                        + Thread.currentThread().getName()
                        + ".slow", 0);

        boolean slowDeliveryDetected = false;

        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
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long traceTag = me.mTraceTag;
            long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
            long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
            if (thresholdOverride > 0) {
                slowDispatchThresholdMs = thresholdOverride;
                slowDeliveryThresholdMs = thresholdOverride;
            }
            final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
            final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

            final boolean needStartTime = logSlowDelivery || logSlowDispatch;
            final boolean needEndTime = logSlowDispatch;

            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }

            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (logSlowDelivery) {
                if (slowDeliveryDetected) {
                    if ((dispatchStart - msg.when) <= 10) {
                        Slog.w(TAG, "Drained");
                        slowDeliveryDetected = false;
                    }
                } else {
                    if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                            msg)) {
                        // Once we write a slow delivery log, suppress until the queue drains.
                        slowDeliveryDetected = true;
                    }
                }
            }
            if (logSlowDispatch) {
                showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", 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(); } } 複製代碼

loop方法是Looper中最核心的方法了,也就是消息循環,這裏一樣是死循環,當有消息的時候就調用next方法,把消息交給Handler分發出去,咱們來看看分發過程:

final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
複製代碼

上面有個msg.target.dispatchMessage(msg);,msg.target就是Handler,哦哦,這就是核心了,這裏就是把msg交給了handler,也就是說處理消息的事轉交給handler了,也就是建立Handler的線程會去執行這些業務。

3.4.1 爲啥消息loop的時候一直阻塞卻不卡死

這個問題是個重點經典的問題啊,不少面試官也喜歡問這個的。這裏作個總結

  • 1.Android應用程序的消息處理機制由消息循環、消息發送和消息處理三個部分組成的。

  • 2.Android應用程序的主線程在進入消息循環過程前,會在內部建立一個Linux管道(Pipe),這個管道的做用是使得Android應用程序主線程在消息隊列爲空時能夠進入空閒等待狀態,而且使得當應用程序的消息隊列有消息須要處理時喚醒應用程序的主線程。

  • 3.Android應用程序的主線程進入空閒等待狀態的方式實際上就是在管道的讀端等待管道中有新的內容可讀,具體來講就是是經過Linux系統的Epoll機制中的epoll_wait函數進行的。

  • 4.當往Android應用程序的消息隊列中加入新的消息時,會同時往管道中的寫端寫入內容,經過這種方式就能夠喚醒正在等待消息到來的應用程序主線程。

  • 5.當應用程序主線程在進入空閒等待前,會認爲當前線程處理空閒狀態,因而就會調用那些已經註冊了的IdleHandler接口,使得應用程序有機會在空閒的時候處理一些事情。

上面是總結的比較好的,在這裏我在更加簡明一點吧:

  • 消息循環採用pipe的epoll機制,epoll一直處於讀端,也便是等着消息。
  • Android系統中全部的操做都會去觸發epoll的寫操做,有寫就會去喚醒,因此就不會卡死了,有的人說,若是系統中沒有操做呢,這就沒有意義了,既然你對手機不作任何操做,那麼卡死不卡死對你的來講也是不可感知了,也就不存在卡死的說法了。

3.5 Handler的工做原理

Handler的做用就是發送和處理消息,主要涉及post,sendMessage和handleMessage函數

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);
    }
複製代碼

能夠很清晰的看到,消息的發送就是放MQ中添加了一條消息而已,MQ收到消息後開始loo,而後調用dispatchMsg方法去分發 、

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

到這裏就是開始handleMessage了,而後咱們會在自定類中去重寫這個方法去處理具體的業務。

ok全部的內容咱們都過了一遍,瞭解這些也就基本瞭解消息的處理機制了,對咱們平時開發和麪試總結仍是頗有幫助的,若是以爲對您有啓發,請手動點贊一下。

相關文章
相關標籤/搜索