歡迎你們前往騰訊雲社區,獲取更多騰訊海量技術實踐乾貨哦~android
做者:汪毅雄bash
導語: 本文講述的是Android的消息機制原理,從Java到Native代碼進行了梳理,並結合其中使用到的Epoll模型予以介紹。服務器
Android的消息傳遞,是系統的核心功能,對於如何使用相信你們都已經至關熟悉了,這裏簡單提一句。咱們能夠粗糙的認爲消息機制中關鍵的幾個類的功能以下:ide
Handler:消息處理者oop
Looper:消息調度者post
MessageQueue:存放消息的地方學習
使用過程:ui
Looper.prepare > #$%^^& > Looper.loop(死循環) --- loop到一個消息 > Handler處理this
好了,咱們直接看源碼吧。spa
消息機制是伴隨線程的,也就是說上面的幾個類在能夠在任何一個線程中都有實例的。
先看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纔是整個消息機制的核心!
接上面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模型呢?我先簡單介紹一下。
爲何要引入呢?
在Looper.loop的時候提到了,android不會簡單粗暴地真的執行啥都沒幹的死循環。剛纔說了,問題出在queue.next。Epoll乾的事就是: 若是你的queue中沒有消息可執行了,好了你能夠歇着了,等有消息的我再告訴你。這個queue.next就是「阻塞」(休眠)在這裏。
Epoll簡單介紹
一、傳統的阻塞型I/O(一邊寫,一邊讀),一個線程只能處理一個一個IO流。
二、若是一個線程想要處理多個流,能夠採用了非阻塞、輪詢I/O方式,可是傳統的非阻塞處理多個流的時候,會遍歷全部流,可是若是全部流都沒數據,就會白白浪費CPU。
。。。
因而出現了select和epoll兩種常見的代理方式。
三、select就是那種無差異輪詢的代理方式。epoll能夠理解爲Event poll,也就是說代理者會代理流的時候也伴隨着事件,所以有了對應事件,就能夠避免無差異輪詢了。
四、其一般的操做有: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層的主要表現就是這些。
最後,畫了一個粗糙、且不太準確圖僅供參考學習
此文已由做者受權騰訊雲技術社區發佈,轉載請註明原文出處