深入理解 Android 訊息機制原理

騰訊雲加社群發表於2017-12-06

歡迎大家前往騰訊雲社群,獲取更多騰訊海量技術實踐乾貨哦~

作者:汪毅雄

導語: 本文講述的是Android的訊息機制原理,從Java到Native程式碼進行了梳理,並結合其中使用到的Epoll模型予以介紹。


Android的訊息傳遞,是系統的核心功能,對於如何使用相信大家都已經相當熟悉了,這裡簡單提一句。我們可以粗糙的認為訊息機制中關鍵的幾個類的功能如下:

Handler:訊息處理者

Looper:訊息排程者

MessageQueue:存放訊息的地方

使用過程:

Looper.prepare > #$%^^& > Looper.loop(死迴圈) --- loop到一個訊息 > Handler處理

好了,我們直接看原始碼吧。

Java層

訊息機制是伴隨執行緒的,也就是說上面的幾個類在可以在任何一個執行緒中都有例項的。

先看Looper吧。以主執行緒為例,Android程式在初始化,會呼叫prepareMainLooper

public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            ...
            sMainLooper = myLooper();
        }
    }複製程式碼


 private static void prepare(boolean quitAllowed) {
        ...
        sThreadLocal.set(new Looper(quitAllowed));
    }複製程式碼


 private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }複製程式碼


以上幾個方法就是Looper初始化,如果是主執行緒Looper會建立一個不可退出的MessageQueue,並把looper例項放入執行緒獨立(ThreadLocal)變數中。

Looper#loop

public static void loop() {
        final Looper me = myLooper();
        final MessageQueue queue = me.mQueue;
        for (;;) {
            Message msg = queue.next(); 
            if (msg == null) {
                return;
            }
            ...
            try {
                msg.target.dispatchMessage(msg);
            } 
            ...
            msg.recycleUnchecked();
        }
    }複製程式碼


Looper prepare後就可以loop了,loop非常簡單,一直去queue中拿訊息就好了,拿到了交給target也就是Handler處理。大家有可能會奇怪這種死迴圈,執行起來不會太sb粗暴了嗎?其實這個解決方式在queue.next!!!後面再講。

Handler#dispatchMessage

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


handler收到後,如果發現message的callback不為空,則只處理callback。(提一句,我們用的很多的handler.post(Runnable),其實這個Runnable就是這裡的callback,也就是說post的Runnable實質上是一個優先順序很高的Message),如果沒有則嘗試交給handler本身的callback處理(handler初始化的時候可以用callback方式構造),再沒有才到我們常用的handleMessage方法,這裡就是我們經常重寫的方法。

再說說訊息的傳送,一般handler會呼叫sendMessage方法,但是最終這個方法還是會跑到這裡

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


再交給MessageQueue

boolean enqueueMessage(Message msg, long when) {
        。。。
        synchronized (this) {
            。。。
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    。。。
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            if (needWake){
               nativeWake(mPtr);
            }
        }
        return true;
    }複製程式碼


MessageQueue會把訊息插入佇列,並依次改變佇列中各個訊息的指標。

咦,好像只用Java層貌似就能把整個訊息機制說通了,native程式碼在哪兒?有何用呢?

但是,剛才提到了Looper初始化的時候也會新建一個MessageQueue

MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }複製程式碼


好了,我們第一個native方法出來了。這時候我們可以猜得到,MessageQueue才是整個訊息機制的核心!

Native層

接上面Java層的程式碼,MessageQueue構造的時候會調一個nativeInit。

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    。。。
}複製程式碼


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


native層呼叫init方法後,會在native層構建一個native Looper!來看看native Looper的初始化

Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
        mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
    mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
    ...
    rebuildEpollLocked();
}複製程式碼


這裡建立了一個eventfd,程式碼來自最新的8.0,這部分和5.0 pipe管道的mWakeReadPipeFd和mWakeWritePipeFd稍微有點不一樣,前者是等待/響應,後者是讀取/寫入。只是android選取方式的不同而已,這塊就不細說。

void Looper::rebuildEpollLocked() {
    。。。
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));
    struct epoll_event eventItem;
    memset(& eventItem, 0, sizeof(epoll_event)); 
    eventItem.events = EPOLLIN;
    eventItem.data.fd = mWakeEventFd;
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
    。。。
}複製程式碼


再到rebuildEpollLocked這個方法中,可以看到通過epoll_create建立了一個epoll專用的檔案描述符,EPOLL_SIZE_HINT表示mEpollFd上能監控的最大檔案描述符數。最後呼叫epoll_ctl監控mWakeEventFd檔案描述符的Epoll事件,即當mWakeEventFd中有內容可讀時,就喚醒當前正在等待的執行緒.。

這裡不瞭解的人可能聽著暈,上面這麼一大段一句話概括就是:Android native層用了Epoll模型。什麼是Epoll模型呢?我先簡單介紹一下。

Epoll(必看!!!)

為什麼要引入呢?

在Looper.loop的時候提到了,android不會簡單粗暴地真的執行啥都沒幹的死迴圈。剛才說了,問題出在queue.next。Epoll乾的事就是: 如果你的queue中沒有訊息可執行了,好了你可以歇著了,等有訊息的我再告訴你。這個queue.next就是“阻塞”(休眠)在這裡。

Epoll簡單介紹

1、傳統的阻塞型I/O(一邊寫,一邊讀),一個執行緒只能處理一個一個IO流。

2、如果一個執行緒想要處理多個流,可以採用了非阻塞、輪詢I/O方式,但是傳統的非阻塞處理多個流的時候,會遍歷所有流,但是如果所有流都沒資料,就會白白浪費CPU。
。。。
於是出現了select和epoll兩種常見的代理方式。

3、select就是那種無差別輪詢的代理方式。epoll可以理解為Event poll,也就是說代理者會代理流的時候也伴隨著事件,因此有了對應事件,就可以避免無差別輪詢了。

4、其通常的操作有:epoll_create(建立一個epoll)、epoll_ctl(往epoll中增加/刪除某一個流的某一個事件)、epoll_wait(在一定時間內等待事件的發生)

eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeEventFd;
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);複製程式碼


好了,我們結合Looper初始化的程式碼來讀一下epoll在這裡幹了什麼吧。

我直接翻譯了:往mEpollFd代理中、註冊、一個叫mWakeEventFd流、的資料流入事件(EPOLLIN)

這樣大家應該懂了吧。。。

接上MessageQueue在初始化後,在native建立了一個Looper。
我們繼續訊息的傳送和提取在native層的表現。其實native層主要負責的是訊息的排程,比如說何時阻塞、何時喚醒執行緒,避免CPU浪費。

native傳送

傳送在native比較簡單,handler傳送訊息後,會到MessageQueue的enqueueMessage,此時線上程阻塞的情況下,會呼叫nativeWake來喚起執行緒。

void NativeMessageQueue::wake() {
    mLooper->wake();
}複製程式碼


 void Looper::wake() {
    。。。
    uint64_t inc = 1;
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
    if (nWrite != sizeof(uint64_t)) {
      。。。。
    }
}複製程式碼


這裡TEMP_FAILURE_RETRY是一個巨集定義,顧名思義,就是不斷地嘗試往mWakeEventFd流裡面寫一個無用資料直到成功,以此來喚醒queue.next。這部分就不多說了。

native訊息提取

也就是queue.next

Message next() {
        。。。
        for (;;) {
           。。。
            nativePollOnce(ptr, nextPollTimeoutMillis);
           。。。
        }
    }複製程式碼


可以看到,又是一個死迴圈(阻塞)。繼續往下看

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
    。。。
    mLooper->pollOnce(timeoutMillis);
    。。。
}複製程式碼


mLooper->pollOnce

mLooper->pollInner

int Looper::pollInner(int timeoutMillis) {
    。。。
    int result = POLL_WAKE;
    mPolling = true;
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

    mPolling = false;
    mLock.lock();

    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        if (fd == mWakeEventFd) {
            if (epollEvents & EPOLLIN) {
                awoken();
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents);
            }
        } 
        。。。
    }
    。。。
    mLock.unlock();
    。。。
    return result;
}複製程式碼


在這裡,我們注意到epoll_wait方法,這裡會得到一段時間內(結合訊息計算得來的)收到的事件個數,這裡對於queue來說就是空閒(阻塞)狀態。過了這個時間後,看看事件數,如果為0,則意味著超時。否則,遍歷所有的事件,看看有沒有mWakeEventFd,且是EPOLLIN事件的,有的話就真正喚醒執行緒、解除空閒狀態。

訊息機制在native層的主要表現就是這些。

最後,畫了一個粗糙、且不太準確圖僅供參考學習

相關閱讀

此文已由作者授權騰訊雲技術社群釋出,轉載請註明原文出處


相關文章