【遷移博客】深刻理解Android消息機制

深刻理解Android消息機制

在平常的開發中,Android 的消息機制做爲系統運行的根本機制之一,顯得十分的重要。java

從 Handler 發送消息開始

查看源碼,Handler的post、send方法最終都會走到android

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 爲異步消息bash

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

若是是新的隊列頭,直接插入隊列異步

若是隊列裏面已經有消息了,執行以下邏輯async

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

插入消息的時候,通常不會喚醒消息隊列。若是消息是異步的,而且隊列頭不是一個異步消息的時候,會喚醒消息隊列ide

if (needWake) {
	nativeWake(mPtr);
}
複製代碼

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

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

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佔用的暴增?

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

  1. 消息隊列構造的時候是否調用了jni部分
  2. 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層的對應圖以下:

handler.png

備註

  1. Java 層和 native 層經過 MessageQueue 裏面持有一個 native 的MessageQueue 對象進行交互。WeakMessageHandler 繼承自MessageHandler,NativeMessageQueue 繼承自 MessageQueue
  2. Java 層和 native 層實質是各自維護了一套類似的消息系統。C層發出的消息和Java層發出的消息能夠沒有任何關係。因此 Framework 層只是很巧的利用了底層 epoll 的機制達到阻塞的目的。
  3. 經過 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發送消息的時候跑出的系統異常的排查也頗有意義。

特別感謝

本次源碼的閱讀過程當中,遇到了不少不了解的問題例如epoll,這裏很是感謝IO哥(查看IO哥大佬)助和指導。讓我在某些細節問題上暫時繞過和恍然大悟。

相關文章
相關標籤/搜索