深入理解 Android 消息機制

原文轉載:深入理解 Android 消息機制
深入理解Android消息機制
在日常的開發中,Android 的消息機制作爲系統運行的根本機制之一,顯得十分的重要。

從 Handler 發送消息開始
查看源碼,Handler的post、send方法最終都會走到

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

sendMessageDelayed 會走到

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

這裏可以設置 Message 爲異步消息

查看 queue 的 enqueueMessage 方法, 我們剝離出核心代碼:

if (p == null || when == 0 || when < p.when) {
    // New head, wake up the event queue if blocked.
    msg.next = p;
    mMessages = msg;
    needWake = mBlocked;
 }

如果是新的隊列頭,直接插入隊列

如果隊列裏面已經有消息了,執行如下邏輯

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;

插入消息的時候,一般不會喚醒消息隊列。如果消息是異步的,並且隊列頭不是一個異步消息的時候,會喚醒消息隊列

if (needWake) {
    nativeWake(mPtr);
}

消息隊列的具體喚醒過程我們暫時不細看。把關注點移到 Looper 上。looper在執行的時候具體執行了什麼邏輯呢?查看 Looper.java 的 looper() 方法

looper 方法中有一個死循環, 在死循環中,會獲取下一個 Message

for (;;) {
    Message msg = queue.next(); // might block
}
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());

當存在一個 barrier 消息的時候,會尋找隊列中下一個異步任務。而不是按照順序。 例如3個消息,1,2,3, 2 是異步消息。如果不存在barrier的時候,next的順序就是 1,2,3 但是如果存在barrier的時候,則是 2,1,3

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 不爲空,就返回,並且將它移出隊列 在 MessageQueue 爲空的時候,會順便去處理一下 add 過的 IdleHandler, 處理一些不重要的消息

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 的源碼。

* Callback interface for discovering when a thread is going to block
     * waiting for more messages.
     */
    public static interface IdleHandler {
        /**
         * Called when the message queue has run out of messages and will now
         * wait for more.  Return true to keep your idle handler active, false
         * to have it removed.  This may be called if there are still messages
         * pending in the queue, but they are all scheduled to be dispatched
         * after the current time.
         */
        boolean queueIdle();
    }

當 queueIdle() 爲 false 的時候,會將它從 mIdleHandlers 中 remove,仔細思考下,我們其實可以利用IdleHandler實現不少功能, 例如

Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
    @Override
    public boolean queueIdle() {
        return false
    }
});

我們可以在 queueIdle 中,趁着沒有消息要處理,統計一下頁面的渲染時間(消息發送完了說明UI已經渲染完了),或者算一下屏幕是否長時間沒操作等等。

拿到 Message 對象後,會將 Message 分發到對應的 target 去

msg.target.dispatchMessage(msg);

查看源碼

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

當 msg 的 callback 不爲 null 的時候,即通過 post(Runnable) 發送信息的會執行 handlerCallback(msg) 方法。如果 mCallback 不爲 null並且 handleMessage 的結果爲 false,則執行 handleMessage 方法。否則會停止分發。

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

查看 handlerCallback 方法源碼, callback 會得到執行。到這裏基本的Android消息機制就分析完了,簡而言之就是,Handler 不斷的將Message發送到一 根據時間進行排序的優先隊列裏面,而線程中的 Looper 則不停的從MQ裏面取出消息,分發到相應的目標Handler執行。

爲什麼主線程不卡?
分析完基本的消息機制,既然 Looper 的 looper 方法是一個for(;;;)循環,那麼新的問題提出來了。爲什麼Android會在主線程使用死循環?執行死循環的時候爲什麼主線程的阻塞沒有導致CPU佔用的暴增?�

繼續分析在源碼中我們沒有分析的部分:

消息隊列構造的時候是否調用了jni部分
nativeWake、nativePollOnce這些方法的作用是什麼
先查看MQ的構造方法:

MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

會發現消息隊列還是和native層有關係,繼續查看android/platform/frameworks/base/core/jni/android_os_MessageQueue_nativeInit.cpp中nativeInit的實現:

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    if (!nativeMessageQueue) {
        jniThrowRuntimeException(env, "Unable to allocate native queue");
        return 0;
    }

    nativeMessageQueue->incStrong(env);
    return reinterpret_cast<jlong>(nativeMessageQueue);
}

這裏會發現我們初始化了一個 NativeMessageQueue ,查看這個消息隊列的構造函數

NativeMessageQueue::NativeMessageQueue() :
        mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        mLooper = new Looper(false);
        Looper::setForThread(mLooper);
    }
}

這裏會發現在mq中初始化了 native 的 Looper 對象,查看android/platform/framework/native/libs/utils/Looper.cpp中 Looper 對象的構造函數

// 簡化後的代碼

Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {

    int wakeFds[2];
    int result = pipe(wakeFds);

    mWakeReadPipeFd = wakeFds[0];
    mWakeWritePipeFd = wakeFds[1];

    result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
    result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);

    mEpollFd = epoll_create(EPOLL_SIZE_HINT);

    struct epoll_event eventItem;
    memset(& eventItem, 0, sizeof(epoll_event)); 
    eventItem.events = EPOLLIN;
    eventItem.data.fd = mWakeReadPipeFd;
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);
}

這裏我們會發現,在 native 層創建了一個epoll,並且對 epoll 的 event 事件進行了監聽。

什麼是epoll

在繼續分析源碼之前,我們先分析一下,什麼是epoll

epoll是Linux中的一種IO多路複用方式,也叫做event-driver-IO。

Linux的select 多路複用IO通過一個select()調用來監視文件描述符的數組,然後輪詢這個數組。如果有IO事件,就進行處理。

select的一個缺點在於單個進程能夠監視的文件描述符的數量存在最大限制,select()所維護的存儲大量文件描述符的數據結構,隨着文件描述符數量的增大,其複製的開銷也線性增長。

epoll在select的基礎上(實際是在poll的基礎上)做了改進,epoll同樣只告知那些就緒的文件描述符,而且當我們調用epoll_wait()獲得就緒文件描述符時,返回的不是實際的描述符,而是一個代表就緒描述符數量的值,你只需要去epoll指定的一個數組中依次取得相應數量的文件描述符即可。

另一個本質的改進在於epoll採用基於事件的就緒通知方式(設置回調)。在select中,進程只有在調用一定的方法後,內核纔對所有監視的文件描述符進行掃描,而epoll事先通過epoll_ctl()來註冊一個文件描述符,一旦基於某個文件描述符就緒時,內核會採用類似callback的回調機制,迅速激活這個文件描述符,當進程調用epoll_wait()時便得到通知

關於epoll和select,可以舉一個例子來表達意思。select的情況和班長告訴全班同學交作業類似,會挨個去詢問作業是否完成,如果沒有完成,班長會繼續詢問。

而epoll的情況則是班長詢問的時候只是統計了待交作業的人數,然後告訴同學作業完成的時候告訴把作業放在某處,然後喊一下他。然後班長每次都去這個地方收作業。

大致瞭解了epoll之後,我們繼續查看nativePollOnce方法,同理,會調用native Looper的pollOnce方法

while (mResponseIndex < mResponses.size()) {
            const Response& response = mResponses.itemAt(mResponseIndex++);
            int ident = response.request.ident;
            if (ident >= 0) {
                int fd = response.request.fd;
                int events = response.events;
                void* data = response.request.data;
                if (outFd != NULL) *outFd = fd;
                if (outEvents != NULL) *outEvents = events;
                if (outData != NULL) *outData = data;
                return ident;
            }
        }

在pollOnce中,會先處理沒有callback的response(ALOOPER_POLL_CALLBACK = -2),處理完後會執行pollInner方法

// 移除了部分細節處理和日誌代碼
// 添加了分析源碼的日誌

int Looper::pollInner(int timeoutMillis) {
    if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {
            nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
            int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime);
            if (messageTimeoutMillis >= 0
                    && (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) {
                timeoutMillis = messageTimeoutMillis;
            }
      }

    // Poll.
    int result = ALOOPER_POLL_WAKE;
    mResponses.clear();
    mResponseIndex = 0;

    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    // 等待事件發生或者超時
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

    // Acquire lock.
    mLock.lock();

    // Check for poll error.
    // epoll 事件小於0, 發生錯誤
    if (eventCount < 0) {
        if (errno == EINTR) {
            goto Done;
        }
        result = ALOOPER_POLL_ERROR;
        goto Done;
    }

    if (eventCount == 0) {
        // epoll事件爲0,超時,直接跳轉到Done
        result = ALOOPER_POLL_TIMEOUT;
        goto Done;
    }

    //循環遍歷,處理所有的事件
    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        if (fd == mWakeReadPipeFd) {
            if (epollEvents & EPOLLIN) {
                awoken();  //喚醒,讀取管道里面的事件
            } else {
            }
        } else {
            ssize_t requestIndex = mRequests.indexOfKey(fd);
            if (requestIndex >= 0) {
                int events = 0;         
                // 處理request,生成response對象,push到相應的Vector
                pushResponse(events, mRequests.valueAt(requestIndex));
            } else {               
            }
        }
    }

Done: ;

    // Invoke pending message callbacks.
    // 發生超時的邏輯處理
    mNextMessageUptime = LLONG_MAX;
    while (mMessageEnvelopes.size() != 0) {
        // 處理Native端的Message
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
        if (messageEnvelope.uptime <= now) {
            // Remove the envelope from the list.
            // We keep a strong reference to the handler until the call to handleMessage
            // finishes.  Then we drop it so that the handler can be deleted *before*
            // we reacquire our lock.
            { // obtain handler
                sp<MessageHandler> handler = messageEnvelope.handler;
                Message message = messageEnvelope.message;
                mMessageEnvelopes.removeAt(0);
                mSendingMessage = true;
                mLock.unlock();
                handler->handleMessage(message);   // 處理消息事件
            } // release handler

            mLock.lock();
            mSendingMessage = false;
            result = ALOOPER_POLL_CALLBACK;   // 設置回調
        } else {
            // The last message left at the head of the queue determines the next wakeup time.
            mNextMessageUptime = messageEnvelope.uptime;
            break;
        }
    }

    // Release lock.
    mLock.unlock();

    // Invoke all response callbacks.
    // 執行回調
    for (size_t i = 0; i < mResponses.size(); i++) {
        Response& response = mResponses.editItemAt(i);
        if (response.request.ident == ALOOPER_POLL_CALLBACK) {
            int fd = response.request.fd;
            int events = response.events;
            void* data = response.request.data;
            int callbackResult = response.request.callback->handleEvent(fd, events, data);
            if (callbackResult == 0) {
                removeFd(fd);  //移除fd
            }
            // Clear the callback reference in the response structure promptly because we
            // will not clear the response vector itself until the next poll.
            response.request.callback.clear();  // 清除reponse引用的回調方法
            result = ALOOPER_POLL_CALLBACK;  // 發生回調
        }
    }
    return result;
}

看到這裏,我們其實可以看出來整體消息模型由 native 和 Java 2層組成,2層各自有自己的消息系統。 Java層通過調用 pollonce 來達到調用底層epoll 讓死循環進入阻塞休眠的狀態,以避免浪費CPU, 所以這也解釋了爲什麼Android Looper的死循環爲什麼不會讓主線程CPU佔用率飆升。

java層和native層的對應圖如下:

image
image
備註

Java 層和 native 層通過 MessageQueue 裏面持有一個 native 的MessageQueue 對象進行交互。WeakMessageHandler 繼承自MessageHandler,NativeMessageQueue 繼承自 MessageQueue
Java 層和 native 層實質是各自維護了一套相似的消息系統。C層發出的消息和Java層發出的消息可以沒有任何關係。所以 Framework 層只是很巧的利用了底層 epoll 的機制達到阻塞的目的。
通過 pollOnce 的分析,可以發現消息的處理其實是有順序的,首先是處理native message,然後處理native request,最後纔會執行java層,處理java層的message
可以在子線程中創建Handler嗎?爲什麼每個線程只會有一個Looper?
在很多時候,我們可以遇到這2個問題。既然看了 Handler 的源碼,那麼,我們就順便分析一下這 2 個問題。

查看Handler的構造方法,無參構造方法最後會調用

public Handler(Callback callback, boolean async) {
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

可以看到,這裏會直接獲取Looper

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

這裏會把每個 Looper 存到相應的ThreadLocal對象中,如果子線程直接創建了Handler,Looper 就會是一個null,所以會直接跑出一個"Can’t create handler inside thread that has not called Looper.prepare()"的RuntimeException

那麼我們是何時把Looper放入ThreadLocal對象的呢?可以在Looper.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));
}

這也解釋了,在每個 Thread 中,只會存在一個 Looper 對象。如果我們想在子線程中正常創建 Handler,就需要提前運行當前線程的 Looper,調用

Looper.prepare()
就不會拋出異常了。

總結
消息機制作爲 Android 的基礎,還是非常有深入瞭解的必要。對於我們遇到Handler發送消息的時候跑出的系統異常的排查也很有意義。

推薦閱讀:

騰訊面試Hander源碼分析:騰訊阿里面試,手寫一段Handler源碼拿下40w的offer_嗶哩嗶哩 (゜-゜)つロ 乾杯~-bilibili

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章