近來許多參加面試的小夥伴大部分都會被問及Handler相關的知識。同時也可以發現網上有大量的各種部落格談來談去,千篇一律無非是把源碼截取過來一段一段的解析。我一直覺得了解了,才能夠記得住,而且即便忘了也能很快的撿起來,是以本篇将會從更多的為什麼來分析Handler。
消息循環機制
我們都知道,Android應用程式是通過消息來驅動的,整個機制是圍繞着消息的産生以及處理而展開的。消息機制的三大要點:消息隊列、消息循環(分發)、消息發送與處理。
1. 消息隊列
Android應用程式線程的消息隊列是使用一個MessageQueue對象來描述的,它可以通過調用Looper類的靜态成員函數
prepareMainLooper
或者
prepare
來建立,其中,前者用來為應用程式的主線程建立消息隊列;後者用來為應用程式的其它子線程建立消息隊列。
- 建立消息隊列
prepareMainLooper
和
prepare
的實作:
public static void prepare() {
prepare(true);
}
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
不管是在主線程中prepare還是在其它線程中,最終調用的方法都是
prepare(boolean quitAllowed)
方法,進一步來看下具體實作。
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//目前線程建立唯一的loop對象
sThreadLocal.set(new Looper(quitAllowed));
}
程式最後一行中,為目前的線程建立唯一的loop對象。
loop的構造方法如下:
private Looper(boolean quitAllowed) {
//建立消息隊列
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
程式到了上面後開始建立消息隊列,MessageQueue的構造方法如下:
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
//調用JNI方法建立消息隊列
mPtr = nativeInit();
}
可以發現java層的MessageQueue是由JNI層的
nativeInit
方法實作的。
到了JNI層IDE上面就看不到具體實作了,這裡推薦一個線上的源碼閱讀位址:點選檢視
在
/frameworks/base/core/jni/android_os_MessageQueue.cpp
找到
android_os_MessageQueue.cpp
檔案,這裡我們先看
nativeInit
方法的實作
sp<MessageQueue> android_os_MessageQueue_getMessageQueue(JNIEnv* env, jobject messageQueueObj) {
//java 層messageQueue與目前JNI的MessageQueue關聯
jint intPtr = env->GetIntField(messageQueueObj, gMessageQueueClassInfo.mPtr);
return reinterpret_cast<NativeMessageQueue*>(intPtr);
}
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
if (!nativeMessageQueue) {
jniThrowRuntimeException(env, "Unable to allocate native queue");
return ;
}
//強引用計數加1
nativeMessageQueue->incStrong(env);
//将指針強轉化為java long類型
return reinterpret_cast<jlong>(nativeMessageQueue);
}
在C++層,實作Java層的MessageQueue建立了NativeMessageQueue與java層的相關聯,并将生成的nativeMessageQueue的記憶體位址傳回到java層。
在NativeMessageQueue的構造方法中,建立了JNI層的loop對象:
NativeMessageQueue::NativeMessageQueue() :
mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
mLooper = Looper::getForThread();
if (mLooper == NULL) {
//建立JNI層的Looper對象
mLooper = new Looper(false);
Looper::setForThread(mLooper);
}
}
找到路徑
/system/core/libutils/Looper.cpp
,我們來看Looper的具體實作。
Looper::Looper(bool allowNonCallbacks) :
mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
mResponseIndex(), mNextMessageUptime(LLONG_MAX) {
int wakeFds[];//準備兩個檔案描述符
int result = pipe(wakeFds);//建立一個管道
LOG_ALWAYS_FATAL_IF(result != , "Could not create wake pipe. errno=%d", errno);
mWakeReadPipeFd = wakeFds[];//管道讀端檔案描述符
mWakeWritePipeFd = wakeFds[];//管道寫端檔案描述符
result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);//将管道讀端設為非阻塞模式
LOG_ALWAYS_FATAL_IF(result != , "Could not make wake read pipe non-blocking. errno=%d",
errno);
result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);//管道寫端同樣設為非阻塞
LOG_ALWAYS_FATAL_IF(result != , "Could not make wake write pipe non-blocking. errno=%d",
errno);
mIdling = false;
// Allocate the epoll instance and register the wake pipe.
mEpollFd = epoll_create(EPOLL_SIZE_HINT);//建立一個epoll專用的檔案描述符
LOG_ALWAYS_FATAL_IF(mEpollFd < , "Could not create epoll instance. errno=%d", errno);
//epoll其中一個專用結構體
struct epoll_event eventItem;
//把結構體清零
memset(& eventItem, , sizeof(epoll_event)); // zero out unused members of data field union
//重新指派
eventItem.events = EPOLLIN;//EPOLLIN :表示對應的檔案描述符可以讀;
eventItem.data.fd = mWakeReadPipeFd;//fd:關聯的檔案描述符;
//epoll_ctl函數用于控制某個epoll檔案描述符上的事件,可以注冊事件,修改事件,删除事件。這裡是添加事件
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);
LOG_ALWAYS_FATAL_IF(result != , "Could not add wake read pipe to epoll instance. errno=%d",
errno);
}
上述代碼中建立的管道非常的重要,它又兩個檔案描述符:
mWakeReadPipeFd
(管道讀端檔案描述符)和
mWakeWritePipeFd
(管道寫端描述符)。首先,當一個線程沒有新的消息處理時,它就會睡眠在這個管道的讀端檔案描述符上,直到有新的消息需要處理為止;其次,當其它線程向這個線程的消息隊列發送一個消息之後,其它線程就會通過這個管道的寫端檔案描述符往這個管道寫入資料,,進而将這個線程喚醒,以便它可以對剛才發送到它的消息隊列中的消息進行處理。
epoll是Linux核心為處理大批量檔案描述符而作了改進的poll,是Linux下多路複用IO接口select/poll的增強版本,它能顯著提高程式在大量并發連接配接中隻有少量活躍的情況下的系統CPU使用率。另一點原因就是擷取事件的時候,它無須周遊整個被偵聽的描述符集,隻要周遊那些被核心IO事件異步喚醒而加入Ready隊列的描述符集合就行了。epoll除了提供select/poll那種IO事件的水準觸發(Level Triggered)外,還提供了邊緣觸發(Edge Triggered),這就使得使用者空間程式有可能緩存IO狀态,減少epoll_wait/epoll_pwait的調用,提高應用程式效率。後面在回答為什麼死循環不會導緻app卡死也是利用了這機制的這個特點。
2. 消息循環過程
在looper中建立完畢消息隊列後,就會進入循環了,我們這裡來看下looper的靜态方法
Looper.loop()
,為了不影響閱讀我把了解都加到代碼的注釋中,之外就不做過多解釋。
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
················
//進入死循環,擷取message并處理它
for (;;) {
//從隊列中擷取下一個消息
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
如果沒有消息循環中止
return;
}
················
msg.target.dispatchMessage(msg);
················
//回收消息即便還在使用
msg.recycleUnchecked();
}
}
我們都知道消息隊列是遵循先進先出的,那麼為什麼會這樣呢?我們開看下Message的結構:
/*package*/ int flags;
/*package*/ long when;
/*package*/ Bundle data;
/*package*/ Handler target;
/*package*/ Runnable callback;
// sometimes we store linked lists of these things
/*package*/ Message next;//連結清單結構
private static final Object sPoolSync = new Object();
private static Message sPool;//本地靜态變量,可避免建立多個Messager
private static int sPoolSize = ;
private static final int MAX_POOL_SIZE = ;
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = ; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
不難發現,Message是連結清單結構。回歸正題,我們接着看MessageQueue如何取Message的
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
//如果隊列已經退出,則傳回空
if (ptr == ) {
return null;
}
//等待的閑置Handdler的數目
int pendingIdleHandlerCount = -; // -1 only during first iteration
//下一個消息執行需要的時間
int nextPollTimeoutMillis = ;
for (;;) {
if (nextPollTimeoutMillis != ) {
//進入睡眠狀态時,将目前線程中挂起的所有Binder指令重新整理到核心驅動程式
Binder.flushPendingCommands();
}
//檢查目前線程是否有新的消息需要處理,具體實作下面會講
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;//取出表頭
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
//目前消息的Handler處理器為空,證明這是個barrier,會攔截目前的隊列,直到不是異步消息為止。
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
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 = -;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
//當線程發現它的消息隊列沒有新的消息需要處理時,不是馬上就進入睡眠等待狀态,而是先調用注冊到它的消息隊列中的IdleHandler對象的成員函數queueIdle,一遍它們有機會線上程空閑時執行一些操作。
if (pendingIdleHandlerCount <
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= ) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, )];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = ; 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);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = ;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = ;
}
JNI層是如何檢測是否有新的消息的呢?我們來看下
nativePollOnce(ptr, nextPollTimeoutMillis);
還是在路徑
/frameworks/base/core/jni/android_os_MessageQueue.cpp
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
jlong ptr, jint timeoutMillis) {
//obj參數指向了一個Java層的MessageQueue對象,參數ptr指向了這個MessageQueue對象的成員變量mPtr,而mPtr儲存的是C++層NativeMessageQueue的位址,是以這裡類型轉換是安全的
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
//調用方法檢查是否有新的消息出現
nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}
···········
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
··········
//mLooper指向了一個C++層的Looper對象,這裡調用其成員函數pollOnce來檢查目前線程是否有新的消息需要處理
mLooper->pollOnce(timeoutMillis);
··········
}
繼續找到路徑
/system/core/libutils/Looper.cpp
:
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = ;
for (;;) {
······
if (result != ) {
······
return result;
}
result = pollInner(timeoutMillis);
}
}
······
int Looper::pollInner(int timeoutMillis) {
······
// Poll.
int result = POLL_WAKE;
······
//監聽在Looper構造方法中建立的epoll執行個體的檔案描述符的IO讀寫事件
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
//如果這些檔案描述符都沒有發生IO讀寫書劍,那麼目前線程就會進入等待狀态,等待時間由timeoutMillis來指定
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
······
//循環檢查哪一個檔案描述符發生了IO讀寫事件
for (int i = ; i < eventCount; i++) {
int fd = eventItems[i].data.fd;
uint32_t epollEvents = eventItems[i].events;
//檢查是否是目前線程管道的讀端檔案描述符
if (fd == mWakeEventFd) {
//是否寫入了新的資料
if (epollEvents & EPOLLIN) {
//讀取目前線程關聯的管道的資料
awoken();
}
······
}
······
}
······
return result;
}
······
void Looper::awoken() {
······
uint64_t counter;
//将與目前線程所關聯的管道資料讀出來,以便可以清理這個管道的就資料。
TEMP_FAILURE_RETRY(read(mWakeEventFd, &counter, sizeof(uint64_t)));
}
3. 消息發送與處理
在前面插播了Message的結構體介紹,那麼作為連結清單的表頭它是在什麼時候給指派的呢?下面我們來一起看下Handler是如何做到消息發送以及處理的。
首先是Handler的構造函數:
public class Handler {
······
final Looper mLooper;
final MessageQueue mQueue;
final Callback mCallback;
final boolean mAsynchronous;
······
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
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;
}
}
一個Handler對應一個消息隊列和一個消息循環。Handler發送消息最終都會走到
sendMessageAtTime
方法
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
//調用messagequeue的enqueueMessage方法
return queue.enqueueMessage(msg, uptimeMillis);
}
一波三折,最後走到的是MessageQueue的
enqueueMessage(Message msg, long when)
方法
boolean enqueueMessage(Message msg, long when) {
······
synchronized (this) {
······
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;//給表頭指派
needWake = mBlocked;
} else {
//通常中途插入隊列的消息是不處理的,除非是barrier或者是早先插入的異步消息
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;
}
//調用native方法喚醒線程處理消息,這裡可以推斷mPtr!=0,因為mQuitting=false
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
下面我們來看如何喚醒線程的,還是找到MessagerQueue.cpp:
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->wake();
}
void NativeMessageQueue::wake() {
//又到loop裡面啦
mLooper->wake();
}
......
//Looper.cpp
void Looper::wake() {
······
uint64_t inc = ;
//通過向管道的寫端檔案描述符中寫入資料,進而來喚起線程
ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
if (nWrite != sizeof(uint64_t)) {
if (errno != EAGAIN) {
LOG_ALWAYS_FATAL("Could not write wake signal to fd %d: %s",
mWakeEventFd, strerror(errno));
}
}
}
上面通過向管道寫入資料來喚醒線程,那麼之後又是怎麼處理消息的呢?
再來看Looper的
loop()
方法:
public static void loop() {
final Looper me = myLooper();
······
final MessageQueue queue = me.mQueue;
······
//處理消息
msg.target.dispatchMessage(msg);
......
msg.recycleUnchecked();
}
}
ok,閉上眼睛,深呼吸。Handler的消息循環機制基本上跟着代碼撸完了一遍,下面通過一些問題來深化對其的了解。
深化了解
1. q:什麼是消息循環機制?
Android應用程式是通過消息來驅動的,整個機制是圍繞着消息的産生以及處理而展開的。消息機制的三大要點:消息隊列、消息循環(分發)、消息發送與處理。
2. q: loop死循環為什麼不會導緻應用卡死?
消息循環資料通信采用的是epoll機制,它能顯著的提高CPU的使用率,另外Android應用程式的主線程在進入消息循環前,會在内部建立一個Linux管道(Pipe),這個管道的作用是使得ANdroid應用主線程在消息隊列唯恐時可以進入空閑等待狀态,并且使得當應用程式的消息隊列有消息需要處理是喚醒應用程式的主線程。也就是說在無消息時,循環處于睡眠狀态,并不會出現卡死情況。
3. q:Handler為什麼能夠處理不同線程的消息?
可以看Handler的構造方法,傳入不同的looper就會處理不同的線程。這裡一個線程隻有一個消息隊列和一個消息循環,而Handler與線程是一對多的關系。
4. 為什麼直接在子線程中初始化Handler會報錯?
我們在主線程直接初始化的Handler是不會報錯的,因為建立主線程的時候,已經先初始化了主線程的loop對象,而子線程中我們如果不初始化loop對象就會報錯。可以看下面ActivityThread的Main方法:
public static void main(String[] args) {
······
//先要建立loop對象
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
}
本文會持續更新,歡迎各位同學拍磚。
參考文章:
1. 《Andorid系統源代碼情景分析》
2. Android消息處理零散分析
以下是本人的公衆号