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
接下來咱們先看看準備操做中作了些什麼框架
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
好的, 能夠看到在建立 Looper 對象的時候, 同時會建立一個 MessageQueue 對象, 將它保存到 Looper 對象的成員變量 mQueue 中, 所以每個 Looper 對象都對應一個 MessageQueue 對象ide
咱們接下來看看 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
能夠看到這裏的流程與 Java 的相反this
接下來看看這個 C++ 的 Looper 對象在實例化的過程當中作了哪些事情
// 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 實例化時作了以下的操做
能夠看到這裏引入了兩個新的名詞, pipe 管道 和 管道監聽管理對象 epoll
pipe 管道: 這個管道在一個線程的消息循環過程當中起到的做用很是大
epoll: epoll 機制是 Linux 爲了同時監聽多個文件描述符的 IO 讀寫事件而設計的 多路複用 IO 接口
到這裏消息循環的準備工做就已經完成了, 這裏分析一下它們的結構圖
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 方法主要作了以下幾件事情
好的, 能夠看到獲取消息的方式是經過 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 循環作了以下的操做
至此一次 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, 其內部流程以下
好的, 至此線程是睡眠的機制也明瞭了, 這裏經過 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 如下三個方法
好的, 接下來咱們先看看如何發送消息的
咱們先看看, 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 執行入消息隊列操做
接下來看看 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.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 管道中寫入一個新的數據的方式喚醒目標線程的
經過上一篇的分析可知, 當 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
接下來咱們看看 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 處理消息主要有三種方式
好的, 消息的發送和處理到這裏就分析結束了, 最後再瞭解一下 MessageQueue 中空閒處理者的相關知識
經過上一篇文章的分析可知 MessageQueue 經過 next 方法經過死循環獲取下一個要處理的 Message, 若當前時刻不存在要處理的消息, 下次循環會進行睡眠操做
public final class MessageQueue {
Message next() {
for (;;) {
// 睡眠操做
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
Message prevMsg = null;
Message msg = mMessages;
......
if (msg != null) {
// ......
return msg;
}
// 空閒時間
.......
}
// 空閒時間
......
}
}
}
複製代碼
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);
}
}
}
複製代碼
經過上述代碼能夠獲得如下的信息
好的, 結下來看看處理細節
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 時, 會進行一些空閒消息的處理
至此 Android 的消息機制就所有結束了, 此前分析過消息機制, 但一直沒有深度到 Native 層, 只是浮於表面, 本次深刻到了 Native 層, 看到了更底層的 epoll 監控管道相關的知識, 能夠說發現了新的天地, 對 Handler 的機制有了更加深入的理解, 本文中有分析的不正確或者不到位的地方, 但願你們多多批評指出, 筆者但願與你們共同成長。