天天酷跑小蜜桃|天天酷跑女角色被日
閱讀更多

0頂
0踩

編程語言

轉載新聞 深入理解Android消息機制

2018-04-20 11:18 by 副主編 jihong10102006 評論(0) 有34061人瀏覽
在日常的開發中,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層的對應圖如下:

備注
  • 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發送消息的時候跑出的系統異常的排查也很有意義。

特別感謝

本次源碼的閱讀過程中,遇到了很多不了解的問題例如epoll,這里非常感謝IO哥(查看IO哥大佬)助和指導。讓我在某些細節問題上暫時繞過和恍然大悟。
  • 大小: 13.9 KB
來自: github
0
0
評論 共 0 條 請登錄后發表評論

發表評論

您還沒有登錄,請您登錄后再發表評論

相關推薦

  • android消息機制

    android 消息機制 深入理解android消息機制 更好開發

  • 手寫一套Java的Handler程序,深入理解Android消息機制

    手寫一套Java的Handler程序,深入理解Android消息機制

  • 深入理解android消息機制

  • 深入理解android 消息機制

  • 從源碼深入理解Android Handler異步消息處理機制

  • android 深入理解Android消息機制

  • 深入理解Android 消息機制(一)——綜述

    在日常開發和學習中,我們肯定都會接觸到Android消息機制。我們知道,在非UI線程中不能直接更新UI,一般我們都是在非UI線程中通過Handler發送一條消息來更新UI。通過Handler可以將任務切換到Handler所在的線程中。Android消息機制主要是指Handler的運行機制,Handler的運行需要和MessageQueue和Looper配合才能完成。可能有人會問:我在Activity

  • 深入理解Android Handler 消息機制

  • Android消息機制——深入理解Handler

  • 深入理解Android卷1.pdf

  • 推薦鄧凡平新作《深入理解Android:Java虛擬機ART》

    今天,收到業內同行的消息:鄧老師出新書啦——《深入理解Android:Java虛擬機ART》,主要內容包括:ART虛擬機源碼分析、oat文件結構、GC、JVM執行原理、多線程管理、dex、JIT等虛擬機核心技術。全書近1000頁,內容詳實,干貨滿滿。

  • 深入理解Android內核設計思想——讀書筆記

  • 深入理解Android卷1(鄧凡平)pdf

  • 深入理解Android內核設計思想(第2版)(上下冊)-試讀版.pdf

    深入理解Android內核設計思想(第2版)(上下冊)-試讀版.pdf

  • [深入理解Android卷一全文-第一章]閱讀前的準備工作

    由于《深入理解Android 卷一》和《深入理解Android卷二》不再出版,而知識的傳播不應該因為紙質媒介的問題而中斷,所以我將在CSDN博客中全文轉發這兩本書的全部內容。(出版社排版好的PDF版正在向出版社申請,到時候會通過CSDN下載資源發布)第一章 ?閱讀前的準備工作本章主要內容本章簡單介紹Android系統架構、編譯環境的搭建以及一些工具的使用。1.1 ?系統架構1.1.1 ?Andro

  • 深入理解Android:卷II》.pdf

  • 深入理解Android卷3_高清PDF版

    本書適合Android系統級開發人員,重點在底層和框架層,這是深入理解Android的第3卷。本書為完整掃描版,文字清晰,排版整齊,整體文件經過壓縮,體積更小,去除了多余重復頁面。并且糾正了錯誤的標簽,歡迎對android系統架構感興趣的朋友下載。

  • 深入理解ANDROID 卷3高清完整PDF版

    深入理解Android(卷3)》是Android經典暢銷書系“深入理解Android”系列Framework卷的第III卷,從源代碼的角度,對Android系統的Audio和UI兩大功能的相關模塊的實現原理和工作機制做了系統且詳細的分析,填補了市場的空白。   《深入理解Android(卷3)》在邏輯上分為4個部分:   Part 01(第1~2章):這是本書的基礎部分,首先介紹了Android源碼環境的搭建、編譯和調試;然后講解了Android進程間通信與任務調度的工具Binder與MessageQueue。這兩項基礎工作是深入研究Android前必須做的功課。   Part 02(第3章):詳細分析了AudioService服務的實現,包括音量管理、音頻外設管理、AudioFocus機制的實現等內容。   Part 03(第4~6章):這是本書的核心內容之一,詳細分析了Android UI的通用實現,依次剖析了WindowManagerService、Android輸入系統、Android控件系統的工作原理。   Part 04(第7~8章):主要分析了SystemUI和Android壁紙相關服務的實現,包括StatusBarManagerService與NotificationManagerService兩個系統服務,以及WallpaperManagerService系統服務、動態壁紙與靜態壁紙的工作原理等內容。   除此之外,在對海量的Android源代碼進行分析的過程中,本書盡可能地對其中的精妙之處進行了分析和解讀,目的是希望幫助讀者領悟其中的優秀設計思想和方法。

  • 深入理解Android Java虛擬機ART相關及勘誤

    公眾號: 神農和朋友們的雜文集 目錄 本書的簡單介紹 本書各章的難度評估 深入學習有什么好處? 本書勘誤表 P14 P21 本書的簡單介紹 《深入理解Android Java虛擬機ART》是一本以Android 7.0 ART JAVA虛擬機為目標的源碼分析書籍。全書花費了將近3年時間, word原稿有1300多頁,最終出版的書籍有900多頁。定價看起來不便宜,16...

  • 【下載】深入理解android系列書籍pdf(卷1,卷2,卷3,卷4)

    深入理解android系列書籍pdf(卷1,卷2,卷3,卷4) 下載鏈接: https://pan.baidu.com/s/1zQ7IeYOr3uKwSwWTeSkxtQ? 提取碼獲取方式:掃描關注下面微信公眾號,回復關鍵字: deandroid ...

Global site tag (gtag.js) - Google Analytics 天天酷跑小蜜桃