Android 消息機制詳解(Android P)

前言

Android 消息機制,一直都是 Android 應用框架層很是重要的一部分,想更加優雅的進行 Android 開發,我想了解消息機制是很是必要的一個過程,此前也分析過不少次 Handler 消息機制, 不過都是浮於 Java 層,對於底層的源碼並無深究,通過一年的努力,筆者對於 Android 應用框架層有了新的認識和理解,這裏從新寫下這篇文章。
本文主要從如下幾點來闡述 Androd 消息機制android

  • 線程消息隊列的建立入口
  • 消息循環的準備
  • 消息循環的啓動
  • 消息的發送與處理

一. 線程消息隊列建立入口

咱們知道, 應用進程主線程初始化的入口是在 ActivityThread.main() 中, 咱們看看他是如何構建消息隊列的數組

public class ActivityThread {

    static volatile Handler sMainThreadHandler;  // set once in main()

    public static void main(String[] args) {
        ......
        // 1. 作一些主線程消息循環的初始操做
        Looper.prepareMainLooper();
        
        ......
        
        // 2. 啓動消息循環
        Looper.loop();
    }
    
}
複製代碼

好的, 能夠看到 ActivityThread 中的消息循環構建過程以下bash

  • 調用 Looper.prepareMainLooper, 作一些準備操做
  • 調用 Looper.loop 真正的開啓了消息循環

接下來咱們先看看準備操做中作了些什麼框架

二. 消息循環的準備

public final class Looper {

    private static Looper sMainLooper;  // guarded by Looper.class
    
    public static void prepareMainLooper() {
        // 1. 調用 prepare 方法真正執行主線程的準備操做
        prepare(false);
        
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            // 2. 調用了 myLooper 方法, 獲取一個 Looper 對象給 sMainLooper 賦值
            sMainLooper = myLooper();
        }
    }

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        // 1.1 new 了一個 Looper 對象
        // 1.2 將這個 Looper 對象寫入 ThreadLocal 中
        sThreadLocal.set(new Looper(quitAllowed));
    }
    
    public static @Nullable Looper myLooper() {
        // 2.1 經過 ThreadLocal 獲取這個線程中惟一的 Looper 對象
        return sThreadLocal.get();
    }
    
    final MessageQueue mQueue;
    final Thread mThread;
    
    private Looper(boolean quitAllowed) {
        // 建立了一個消息隊列
        mQueue = new MessageQueue(quitAllowed);
        // 獲取了當前的線程
        mThread = Thread.currentThread();
    }
    
}
複製代碼

能夠看到 Looper.prepareMainLooper 中主要作了以下幾件事情async

  • 調用 prepare 方法真正執行主線程的準備操做
    • 建立了一個 Looper 對象
      • 建立了 MessageQueue 這個消息隊列, 保存到成員變量 mQueue 中
      • 獲取該對象建立線程, 保存到成員變量 mThread 中
    • 將這個 Looper 對象存入 ThreadLocal 中
  • 調用 myLooper 方法, 從 ThreadLocal 中獲取剛剛寫入的 Looper 對象
  • 將這個 Looper 對象, 保存到靜態變量 sMainLooper 中, 表示這個是當前應用進程主線程的 Looper 對象

好的, 能夠看到在建立 Looper 對象的時候, 同時會建立一個 MessageQueue 對象, 將它保存到 Looper 對象的成員變量 mQueue 中, 所以每個 Looper 對象都對應一個 MessageQueue 對象ide

咱們接下來看看 MessageQueue 對象建立時, 作了哪些操做函數

MessageQueue 的建立

public final class MessageQueue {
    
    private final boolean mQuitAllowed; // true 表示這個消息隊列是可中止的
    private long mPtr;                  // 描述一個 Native 的句柄
    
    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        // 獲取一個 native 句柄
        mPtr = nativeInit();
    }
    
    private native static long nativeInit();

}
複製代碼

好的, 能夠看到 MessageQueue 對象在建立的過程當中, 會調用 nativeInit 來獲取一個 native 的句柄, 咱們看看這個 nativeInit 作了哪些操做oop

// frameworks/base/core/jni/android_os_MessageQueue.cpp
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    // 1. 建立了一個 NativeMessageQueue 對象
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    ......
    // 增長 env 對它的強引用計數
    nativeMessageQueue->incStrong(env);
    // 2. 將這個 NativeMessageQueue 對象強轉成了一個句柄返回 Java 層
    return reinterpret_cast<jlong>(nativeMessageQueue);
}


class NativeMessageQueue : public MessageQueue, public LooperCallback {
public:
    NativeMessageQueue();
    ......
private:
    JNIEnv* mPollEnv;
    jobject mPollObj;
    jthrowable mExceptionObj;
};

NativeMessageQueue::NativeMessageQueue() :
        mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    // 1.1 嘗試調用 Looper.getForThread 獲取一個 C++ 的 Looper 對象
    mLooper = Looper::getForThread();
    // 1.2 若當前線程沒有建立過 Looper, 則走這個分支
    if (mLooper == NULL) {
        // 建立 Looper 對象
        mLooper = new Looper(false);
        // 給當前線程綁定這個 Looper 對象
        Looper::setForThread(mLooper);
    }
}
複製代碼

好的能夠看到 nativeInit 方法主要作了以下的操做ui

  • 建立了一個 C++ 的 NativeMessageQueue 對象
    • 嘗試經過 Looper.getForThread 獲取一個 C++ 的 Looper 對象
    • 若當前線程沒有建立過 Looper
      • new 一個 C++ 的 Looper 對象
      • 給當前線程綁定 Looper 對象

能夠看到這裏的流程與 Java 的相反this

  • Java 是先建立 Looper, 而後在 Looper 內部建立 MessageQueue
  • Native 是先建立 NativeMessageQueue, 在其建立的過程當中建立 Looper

接下來看看這個 C++ 的 Looper 對象在實例化的過程當中作了哪些事情

Looper(Native) 的實例化

// system/core/libutils/Looper.cpp
Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
        mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
    // 1. 建立 pipe 管道, 返回該管道文件讀寫的描述符
    mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
    AutoMutex _l(mLock);
    // 處理 epoll 相關事宜
    rebuildEpollLocked();
}

void Looper::rebuildEpollLocked() {
    ......
    // 2. 建立一個 epoll 對象, 將其文件描述符保存在成員變量 mEpollFd 中
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    // 3. 將 pipe 管道的文件讀寫描述符 mWakeEventFd, 添加到 epoll 中, 讓 epoll 對該管道的讀寫進行監聽
    struct epoll_event eventItem;
    memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
    eventItem.events = EPOLLIN;
    eventItem.data.fd = mWakeEventFd;
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
    ......
}
複製代碼

好的, 能夠看到 Looper 實例化時作了以下的操做

  • 調用 eventfd 建立了一個 pipe 管道, 返回了該管道的文件讀寫描述符
  • 建立了一個 epoll 對象
  • 將 pipe 管道的文件讀寫描述符 mWakeEventFd, 添加到 epoll 中, 讓 epoll 對該管道的讀寫進行監聽

能夠看到這裏引入了兩個新的名詞, pipe 管道管道監聽管理對象 epoll

  • pipe 管道: 這個管道在一個線程的消息循環過程當中起到的做用很是大

    • 當一個線程沒有新的消息須要處理時, 它就會睡眠在這個管道的讀端文件描述符上, 直到有新的消息須要處理爲止
    • 當其餘線程向這個線程發送了一個消息以後, 其餘線程就會經過這個管道的寫端文件描述符寫入一個數據, 從而將這個線程喚醒, 以便它能夠對剛纔發送到它消息隊列中的消息進行處理
  • epoll: epoll 機制是 Linux 爲了同時監聽多個文件描述符的 IO 讀寫事件而設計的 多路複用 IO 接口

    • 當 epoll 監聽了大量文件描述符的 IO 讀寫事件, 但只有少許的文件描述符是活躍的, 那麼這個 epoll 會顯著的減小 CPU 的使用率

相互依賴的結構圖

到這裏消息循環的準備工做就已經完成了, 這裏分析一下它們的結構圖

消息循環相互依賴關係圖

三. 消息循環的啓動

public final class Looper {
    
    public static void loop() {
        // 1. 獲取調用線程的 Looper 對象
        final Looper me = myLooper();
        ......
        // 2. 獲取 Looper 對應的消息隊列
        final MessageQueue queue = me.mQueue;
        // 3. 死循環, 不斷的處理消息隊列中的消息
        for (;;) {
            // 3.1 獲取消息隊列中的消息, 取不到消息會阻塞
            Message msg = queue.next(); // might block
            if (msg == null) {
                // 若取到的消息爲 null, 這個 Looper 就結束了
                return;
            }
            ......
            try {
                // 3.2 處理消息
                msg.target.dispatchMessage(msg);
            } finally {
                ......
            }
            ......
        }
    }
    
}
複製代碼

好的, 能夠看到 Looper 的 loop 方法主要作了以下幾件事情

  • 從方法調用的線程中取 Looper 對象
  • 獲取這個 Looper 對應的消息隊列
  • 經過死循環, 不斷地處理消息隊列中的數據
    • 經過 MessageQueue.next() 獲取下一條要處理的 Msg
    • 經過 msg.target.dispatchMessage(msg); 分發處理消息(本次不細究)

好的, 能夠看到獲取消息的方式是經過 MessageQueue.next 拿到的, 咱們接下來看看它是如何獲取的

從隊列中取消息

public final class MessageQueue {
    
    Message next() {
        // 獲取 NativeMessageQueue 的句柄
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
        // 描述空閒事件處理者的數量, 初始值爲 -1 
        int pendingIdleHandlerCount = -1; 
        // 描述當前線程沒有新消息處理時, 可睡眠的時間
        int nextPollTimeoutMillis = 0;
        // 死循環, 獲取可執行的 Message 對象
        for (;;) {
            // 1. 若 nextPollTimeoutMillis 不爲 0, 則說明距離下一個 Message 執行, 有必定的時間間隔
            if (nextPollTimeoutMillis != 0) {
                // 在空閒期間, 執行 Binder 通訊相關的指令
                Binder.flushPendingCommands();
            }
            // 2. 這裏調用了 nativePollOnce, 在 native 層檢查消息隊列中是否有 msg 可讀, 若無可執行的 msg, 則執行線程的睡眠, 時間由 nextPollTimeoutMillis 決定
            nativePollOnce(ptr, nextPollTimeoutMillis);
            // 3. 取隊列中下一條要執行的 Message 對象, 並返回出去
            synchronized (this) {
                final long now = SystemClock.uptimeMillis(); // 獲取當前時刻
                Message prevMsg = null;
                Message msg = mMessages;
                // 3.1 移除消息隊列中無效的 Message 
                // Condition: msg 不爲 null & msg 的處理者爲 null
                if (msg != null && msg.target == null) {
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                // 3.2 隊列首部存在有效的 msg 
                if (msg != null) {
                    // 3.2.1 若當前的時刻 早於 隊首消息要執行的時刻
                    if (now < msg.when) {
                        // 給 nextPollTimeoutMillis 賦值, 表示當前線程, 可睡眠的時間
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 3.2.2 若當前的時刻 不小於 隊首消息要執行的時刻
                        mBlocked = false;// 更改標記位置
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        // 將隊首消息返回出去
                        return msg;
                    }
                } else {
                    // 3.3 說明消息隊列中無消息, 則給 nextPollTimeoutMillis 置爲 -1, // 表示能夠無限睡眠, 直至消息隊列中有消息可讀
                    nextPollTimeoutMillis = -1;
                }
                // 4. 獲取一些空閒事件的處理者
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                // 若無空閒事件, 則進行下一次 for 循環
                if (pendingIdleHandlerCount <= 0) {
                    mBlocked = true;
                    continue;
                }
                .......
            }

            // 4.1 處理空閒事件
            ......
            
            // 4.2 走到這裏, 說明全部的空閒事件都已經處理好了
            // 將須要處理的空閒事件,置爲 0 
            pendingIdleHandlerCount = 0;

            // 5. 由於處理空閒事件是耗時操做, 期間可能有新的 Message 入隊列, 所以將可睡眠時長置爲 0, 表示須要再次檢查
            nextPollTimeoutMillis = 0;
        }
    }
    
    // native 方法
    private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
 
}
複製代碼

能夠看到 MessageQueue.next 內部維護了一個死循環, 用於獲取下一條 msg, 這個 for 循環作了以下的操做

  • 若 nextPollTimeoutMillis 不爲 0, 則說明距離下一個 Message 執行, 有必定的時間間隔
    • 在空閒期間, 執行 Binder 通訊相關的指令
  • 調用 nativePollOnce 根據 nextPollTimeoutMillis 時長, 執行當前線程的睡眠操做
  • 取隊列中下一條要執行的 Message
    • 移除消息隊列中無效的 Message
    • 隊列首部存在有效的 msg
      • 若當前的時刻 < 隊首消息要執行的時刻
        • 則更新 nextPollTimeoutMillis, 下次進行 for 循環時, 會進行睡眠操做
      • 若當前的時刻 >= 隊首消息要執行的時刻
        • 則將隊首消息的描述返回出去
    • 隊列首部不存在有效的 msg
      • 將 nextPollTimeoutMillis 置爲 -1, 下次 for 循環時能夠無限睡眠, 直至消息隊列中有消息可讀
  • 空閒消息的獲取和處理(本次不細究)
    • 獲取空閒事件的處理者
    • 若無空閒事件, 則進行下一次 for 循環
    • 若存在空閒事件, 則處理空閒事件
      • 將 pendingIdleHandlerCount 置爲 0, 表示空閒事件都已經處理完成了
      • 將 nextPollTimeoutMillis 置爲 0, 由於處理空閒事件是耗時操做, 期間可能有新的 Message 入隊列, 所以將可睡眠時長置爲 0, 表示須要再次檢查

至此一次 for 循環就結束了, 能夠看到 Message.next() 中其餘的邏輯都很是的清晰, 但其睡眠是一個 native 方法, 咱們繼續看看它的內部實現

消息隊列中無消息時線程睡眠的實現

// frameworks/base/core/jni/android_os_MessageQueue.cpp
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jlong ptr, jint timeoutMillis) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    // 調用了 NativeMessageQueue 的 pollOnce
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
    ......
    // 1. 調用了 Looper 的 pollOne
    mLooper->pollOnce(timeoutMillis);
    ......
}

// system/core/libutils/Looper.cpp
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    for (;;) {
        ......
        // result 不爲 0 說明讀到了消息
        if (result != 0) {
            ......
            return result;
        }
        // 2. 若未讀到消息, 則調用 pollInner
        result = pollInner(timeoutMillis);
    }
}


int Looper::pollInner(int timeoutMillis) {
    ......
    int result = POLL_WAKE;
    ......
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    // 3. 調用 epoll_wait 來監聽 pipe 中的 IO 事件, 若無事件, 則睡眠在文件讀操做上, 時長由 timeoutMillis 決定
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    // 4. 走到這裏, 說明睡眠結束了
    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;              // 獲取文件描述
        uint32_t epollEvents = eventItems[i].events;
        // 5. 若爲喚醒事件的文件描述, 則執行喚醒操做
        if (fd == mWakeEventFd) {
            if (epollEvents & EPOLLIN) {
                awoken();// 喚醒
            } else {
                ......
            }
        } else {
           ......
        }
    }
    ......
    return result;
}
複製代碼

好的能夠看到 JNI 方法 nativePollOnce, 其內部流程以下

  • 將 Poll 操做轉發給了 Looper.pollOne
  • 若未讀到消息, 則調用 Looper.pollInner
  • 調用 epoll_wait 來監聽 pipe 中的 IO 事件, 若無事件, 則睡眠在文件讀操做上, 時長由 timeoutMillis 決定
  • 睡眠結束後調用 awoken 喚醒

好的, 至此線程是睡眠的機制也明瞭了, 這裏經過 UML 圖總結一下, 線程消息隊列的建立與循環

線程消息的建立與循環的流程圖

四. 消息的發送與處理

咱們都知道, Android 系統提供了 Handler 類, 描述一個消息的處理者, 它負責消息的發送與處理

public class Handler {
   
   final Looper mLooper;
   final MessageQueue mQueue;
   final Callback mCallback;
   
   public Handler() {
       this(null, false);
   }
   
   public Handler(Callback callback, boolean async) {
       // 調用該方法的線程的 Looper
       mLooper = Looper.myLooper();
       // 這裏扔出了 Runtime 異常, 所以 Handler 是沒法在沒有 Looper 的線程中執行的
       if (mLooper == null) {
           throw new RuntimeException(
               "Can't create handler inside thread " + Thread.currentThread()
                       + " that has not called Looper.prepare()");
       }
       // 獲取消息隊列
       mQueue = mLooper.mQueue;
       // 給 Callback 賦值
       mCallback = callback;
       ......
   }
   
   public final boolean sendMessage(Message msg){
       ......
   }
   
   public void handleMessage(Message msg) {
   
   }
   
}
複製代碼

好的, 能夠看到 Handler 的結構如上述代碼所示, 本次只 focus 如下三個方法

  • 構造方法
    • 獲取當前線程的 Looper
    • 獲取當前線程的 MessageQueue
    • 給 Callback 賦值(這個 Callback 的做用, 在後面描述)
  • 發送消息的方法
    • sendMessage
  • 處理消息的方法
    • handleMessage

好的, 接下來咱們先看看如何發送消息的

消息的發送

咱們先看看, Android 中的消息是如何發送的

public class Handler {
   
   ......
   public final boolean sendMessage(Message msg) {
       // 回調了發送延時消息的方法
       return sendMessageDelayed(msg, 0);
   }
   
   public final boolean sendMessageDelayed(Message msg, long delayMillis) {
       if (delayMillis < 0) {
           delayMillis = 0;
       }
       // 回調了發送指定時刻消息的方法
       return sendMessageAtTime(msg, /*指定時刻 = 當前時刻 + 延時時間*/SystemClock.uptimeMillis() + delayMillis);
   }
   
   public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
       MessageQueue queue = mQueue;
       ......
       // 回調了入消息隊列的方法
       return enqueueMessage(queue, msg, uptimeMillis);
   }
   
   private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
       // 將這個消息的處理者, 設置爲其自身
       msg.target = this;
       ......
       // 調用 MessageQueue 的 enqueueMessage 執行入隊列的操做
       return queue.enqueueMessage(msg, uptimeMillis);
   }
}
複製代碼

能夠看到發送消息的操做, 進過了一系列方法的調用, 會走到 sendMessageAtTime 中, 表示發送指定時刻的消息, 而後會調用 enqueueMessage 執行入消息隊列操做

  • 將這個消息的 target 賦值爲自身, 表示這個消息到時候會被當前 Handler 對象執行
  • 調用了 MessageQueue.enqueueMessage 將消息投遞到消息隊列中去

接下來看看 MessageQueue.enqueueMessage 作了哪些操做

public final class MessageQueue {

    boolean enqueueMessage(Message msg, long when) {
        ......
        synchronized (this) {
            ......
            msg.when = when;           // 將消息要執行的時刻寫入成員變量
            Message p = mMessages;     // 獲取當前隊首的消息
            boolean needWake;          // 描述是否要喚醒該 MessageQueue 所綁定的線程
            // Case1: 
            // 1. p == null, 隊列爲空
            // 2. 入隊列消息須要當即執行
            // 3. 入隊列消息執行的時間 早於 當前隊首消息執行的時間
            if (p == null || when == 0 || when < p.when) {
                // 將該消息放置到隊首
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;  // 隊首元素變動了, 有可能須要當即執行
            } else {
            // Case2: 入隊列的消息執行時間 晚於 隊首消息執行時間
                ......
                // 將該消息插入到消息隊列合適的位置
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    // 將 needWake 置爲 false, 由於該消息插入到了後面, 所以不須要喚醒
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p;
                prev.next = msg;
            }

            // 處理該消息隊列綁定線程的喚醒操做
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
    
    private native static void nativeWake(long ptr);

}
複製代碼

能夠看到 MessageQueue 在執行消息入隊列時, 作了以下操做

  • 隊列爲空/入隊列消息須要當即執行/入隊列消息執行的時間早於當前隊首消息執行的時間
    • 將該消息插入到隊列的首部
    • 須要當即喚醒 MessageQueue 綁定的線程
  • 入隊列的消息執行時間 晚於 隊首消息執行時間
    • 將該消息按照時間從早到晚的順序插入隊列
    • 不須要當即喚醒 MessageQueue 綁定的線程
  • 根據 flag 處理該消息隊列綁定線程的喚醒操做

消息入隊列的過程仍是很清晰明瞭的, 從上一篇文章的分析中咱們知道, 若 MessageQueue 在當前時刻沒有要執行的消息時, 會睡眠在 MessageQueue.next() 方法上, 接下來看看它是如何經過 nativeWake 喚醒的

// frameworks/base/core/jni/android_os_MessageQueue.cpp
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->wake();
}

void NativeMessageQueue::wake() {
    mLooper->wake();
}

// system/core/libutils/Looper.cpp
void Looper::wake() {
    // 向 Looper 綁定的線程 pipe 管道中寫入一個新的數據
    uint64_t inc = 1;
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
    ....
}
複製代碼

能夠看到, nativeWake 是經過向 Looper 綁定的線程 pipe 管道中寫入一個新的數據的方式喚醒目標線程的

  • 經過上一篇的分析可知, 此時 Looper::pollInner 中 epoll 在讀操做上的睡眠便會中止

消息的處理

經過上一篇的分析可知, 當 MessageQueue.next 返回一個 Message 時, Looper 中的 loop 方法便會處理消息的執行, 先回顧一下代碼

public final class Looper {
    
    public static void loop() {
        final Looper me = myLooper();
        ......
        final MessageQueue queue = me.mQueue;
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // 若取到的消息爲 null, 這個 Looper 就結束了
                return;
            }
            ......
            try {
                // 處理消息
                msg.target.dispatchMessage(msg);
            } finally {
                ......
            }
            ......
        }
    }
    
}
複製代碼

好的, 能夠看到當 MessageQueue.next 返回一個 Message 時, 便會調用 msg.target.dispatchMessage(msg) 去處理這個 msg

  • msg.target 爲一個 Handler 對象, 在上面的分析中可知, 在經過 Handler 發送消息的 enqueueMessage 方法中, 會將 msg.target 設置爲當前的 Handler
  • 能夠看到, 這個 msg 正是由將它投遞到消息隊列的 Handler 處理了, 它們是一一對應的

接下來咱們看看 Handler 處理消息的流程

public class Handler {
     
    public void dispatchMessage(Message msg) {
        // 1. 若 msg 對象中存在 callback, 則調用 handleCallback
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            // 2. 若當前 Handler 設值了 Callback, 進入這個分支
            if (mCallback != null) {
                // 2.1 若這個 Callback 的 handleMessage 返回 true, 則不會將消息繼續向下分發
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            // 3. 若消息沒有被 mCallback 攔截, 則會調用 handleMessage 進行最後的處理
            handleMessage(msg);
        }
    } 
    
    /**
     * 方式一: 優先級最高
     */
    private static void handleCallback(Message message) {
        message.callback.run();
    }
    
    public interface Callback {
        /**
         * 方式二: 優先級次高
         * @return True if no further handling is desired
         */
        public boolean handleMessage(Message msg);
    }
     
    public Handler(Callback callback, boolean async) {
        ......
        // Callback 由構造函數傳入
        mCallback = callback;
        ......
    } 
    
    /**
     * 方式三: 這個處理消息的方式, 由子類重寫, 優先級最低
     */
    public void handleMessage(Message msg) {
    
    }
    
 }
複製代碼

能夠看到 Handler 的 dispatchMessage 處理消息主要有三種方式

  • 如果 Message 對象內部設置了 callback, 則調用 handleCallback 方法直接處理, 不會再往下分發
  • 若 Handler 設置 Callback, 則會調用 Callback.handleMessage 方法
    • Callback.handleMessage 返回 false, 則會將消息處理繼續分發給 Handler.handleMessage

好的, 消息的發送和處理到這裏就分析結束了, 最後再瞭解一下 MessageQueue 中空閒處理者的相關知識

空閒處理者的添加與處理

1. 什麼是空閒處理者

經過上一篇文章的分析可知 MessageQueue 經過 next 方法經過死循環獲取下一個要處理的 Message, 若當前時刻不存在要處理的消息, 下次循環會進行睡眠操做

  • 在沒有取到可執行消息 ---> 下次 for 循環進行睡眠 之間的時間間隔, 稱之爲空閒時間
  • 在空閒時間處理事務的對象, 稱之爲空閒處理者
public final class MessageQueue {

   Message next() {
        for (;;) {
            // 睡眠操做
            nativePollOnce(ptr, nextPollTimeoutMillis);
            synchronized (this) {
                Message prevMsg = null;
                Message msg = mMessages;
                ......
                if (msg != null) {
                    // ...... 
                    return msg;
                }
                
                // 空閒時間
                ....... 
            }

            // 空閒時間
            ......
            
        }
    }
    
}
複製代碼

2. 空閒處理者的添加

public final class MessageQueue {
    
    public static interface IdleHandler {
        /**
         * 處理空閒消息
         */
        boolean queueIdle();
    }
    
    // 空閒消息集合
    private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
    
    public void addIdleHandler(@NonNull IdleHandler handler) {
        synchronized (this) {
            mIdleHandlers.add(handler);
        }
    }
    
}
複製代碼

經過上述代碼能夠獲得如下的信息

  • 空閒處理者使用 IdleHandler 接口描述
  • 空閒處理者經過 MessageQueue.addIdleHandler() 添加
  • 空閒處理者使用 MessageQueue.mIdleHandlers 維護

好的, 結下來看看處理細節

3. 空閒消息的處理

public final class MessageQueue {

    // 空閒消息集合
    private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
    // 空閒消息處理者的數組
    private IdleHandler[] mPendingIdleHandlers;
    
    Message next() {
        ...... 
        for (;;) {
            ......
            synchronized (this) {
                // 省略獲取 msg 的代碼
                ......
                // 1. 從空閒消息集合 mIdleHandlers 中獲取 空閒處理者 數量
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                // 2 若無空閒處理者, 則進行下一次 for 循環
                if (pendingIdleHandlerCount <= 0) {
                    mBlocked = true;
                    continue;
                }
                ......
                // 3. 將空閒消息處理者集合轉爲數組
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // 4. 處理空閒消息
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];// 獲取第 i 給位置的空閒處理者
                mPendingIdleHandlers[i] = null; // 置空
                boolean keep = false;        
                try {
                    // 4.1 處理空閒消息
                    keep = idler.queueIdle(); 
                } catch (Throwable t) {
                    ......
                }
                if (!keep) {
                    synchronized (this) {
                        // 4.2 走到這裏表示它是一次性的處理者, 從 mIdleHandlers 移除
                        mIdleHandlers.remove(idler);
                    }
                }
            }
            ......
        }
    }
    
}
複製代碼

好的, 能夠看到 MessageQueue.next 在獲取不到 msg 時, 會進行一些空閒消息的處理

  • 從空閒消息集合 mIdleHandlers 中獲取 空閒處理者 數量
  • 若無空閒處理者, 則進行下一次 for 循環
  • 若存在空閒處理者, 則空閒消息處理者集合轉爲數組 mPendingIdleHandlers
  • for 循環處理空閒消息
    • 調用 IdleHandler.queueIdle 處理空閒消息
      • 返回 true, 下次再 MessageQueue.next 獲取不到 msg 的空閒時間會繼續處理
      • 返回 false 表示它是一次性的處理者, 從 mIdleHandlers 移除

總結

至此 Android 的消息機制就所有結束了, 此前分析過消息機制, 但一直沒有深度到 Native 層, 只是浮於表面, 本次深刻到了 Native 層, 看到了更底層的 epoll 監控管道相關的知識, 能夠說發現了新的天地, 對 Handler 的機制有了更加深入的理解, 本文中有分析的不正確或者不到位的地方, 但願你們多多批評指出, 筆者但願與你們共同成長。

相關文章
相關標籤/搜索