歡迎大家前往騰訊雲社區,獲取更多騰訊海量技術實踐干貨哦~
作者:汪毅雄
導語: 本文講述的是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層的主要表現就是這些。
最后,畫了一個粗糙、且不太准確圖僅供參考學習
相關閱讀
此文已由作者授權騰訊雲技術社區發布,轉載請注明原文出處