1、概念
Handler可以通過與線程相關的MessageQueue發送和處理Message和Runnable對象。通過這種機制,Android可以在相同進程中不同線程間通信。
2、Handler類
在源碼中,Handler類中持有對Looper與MessageQueue的引用,如下:
這兩個變量在構造方法執行時會賦值,Handler提供了多個構造方法,如下
通過傳入不同的Looper及Callback可以構造不同的Handler對象。比較常用的是不傳任何參數的第一個構造方法。這些方法最終都會調用如上圖中的第四個構造方法,其中主要邏輯如下:
public Handler(Callback callback, boolean async) {
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
構造方法中,首先會獲取當前線程的Looper對象,如果爲空的話則拋出異常。下面是Looper中myLooper的方法:
從代碼中可以看出,Looper獲取的是與當前線程關聯的Looper對象。而這個對象的設置是在prepare()方法中, 如下
所以在一個新的線程中使用Handler時,如果不調用Looper.prepare()方法會拋出異常。而在主線程中直接new Handler()沒有報錯,是因爲在主線程啓動時調用了相關的代碼,如下
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("<pre-initialized>");
// 創建主線程的looper
Looper.prepareMainLooper();
// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
// It will be in the format "seq=114"
long startSeq = 0;
if (args != null) {
for (int i = args.length - 1; i >= 0; --i) {
if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
startSeq = Long.parseLong(
args[i].substring(PROC_START_SEQ_IDENT.length()));
}
}
}
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
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");
}
有了Handler對象之後,我們就可以使用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);
}
從代碼中可以看到主要的邏輯是調用了enqueueMessage方法,即將消息加入隊列。enqueueMessage的方法如下:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
// 指定msg的target對象,即爲當前Handler
msg.target = this;
// 設置消息是否同步,一般的消息都是同步的。源碼中有一些用到了異步的消息。
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// 此處的queue是MessageQueue對象,通過Looper對象獲取。
return queue.enqueueMessage(msg, uptimeMillis);
}
從代碼中可以看出,Handler的sendMessage只是將message加入到了MessageQueue的隊列中。 至此Handler完成了消息的發送。
3、MessageQueue
上節中Handler發送消息後實際上是調用了MessageQueue的enqueueMessage方法,代碼如下:
boolean enqueueMessage(Message msg, long when) {
// message的target類型爲Handler。Handler中調用enqueueMessage方法時已經指定了該message的target對象了。
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
// 將消息標記爲正在使用
msg.markInUse();
msg.when = when;
Message p = mMessages; // mMessages相當於鏈表的頭節點。Message相當於鏈表中的節點,持有下一個節點的引用next。
boolean needWake;
// 如果當前節點爲空 或 msg的觸發時間爲0 或 觸發的時間早於頭節點,則將該msg插入到頭節點,並賦值爲新的頭節點
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg; // 將msg設置爲新的頭節點
needWake = mBlocked; // 根據block狀態來確定是否需要喚醒
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
// 如果消息是異步的 或 target爲空的情況需要wake
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;
}
}
// 此時p是null,相當於將p插入到鏈表尾部。
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
// 調用native方法喚醒(因爲looper對象在for循環中調用next方法取消息,而取消息的方法會阻塞線程)
nativeWake(mPtr);
}
}
return true;
}
該方法相當於將消息放到了消息鏈表的尾部,同時根據消息是否是同步還是異步來喚醒線程。
至此,已經將消息放到鏈表這樣的數據結構了,接下來肯定是要取出這些消息,然後進行相應的操作的。 消息的取出是通過Looper調用了MessageQueue的next方法實現的,next方法的代碼如下:
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 == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// 根據nextPollTimeoutMillis的值不同,這裏可能會發生阻塞
// -1時會無限阻塞,直到被喚醒。0表示不阻塞,直接返回。其他正數,則會阻塞相應的時間,然後返回。
// 所以剛進來沒有消息時,會一直阻塞在這裏。
// 在handler發送消息後,MessageQueue的enqueueMessage方法中就會喚醒這裏的線程,跳過這裏,從而取出消息處理。
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;
// target爲空的情況一般在源碼中。
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
// 當鏈表頭message的target爲空的情況下,找到第一個異步消息。故這之間的同步消息會被忽略掉。
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
// 如果消息的處理時間還未到,則設置nextPollTimeoutMillis
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; // 將prevMsg與msg後面的節點鏈接起來。
} 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;
}
// 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.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
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);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// 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 = 0;
}
}
通過該方法,就可以取出一個消息了。拿到消息後,Looper中就會做相應處理。
3.1、MessageQueue的native層
在MessageQueue的源碼中使用如下的native代碼:
private native static long nativeInit();
private native static void nativeDestroy(long ptr);
private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
private native static void nativeWake(long ptr);
private native static boolean nativeIsPolling(long ptr);
private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
在native層與之對應的類是NativeMessageQueue。通過對源碼的查看,可以得知MessageQueue的阻塞與喚醒是通過epoll機制實現的。(這裏還需要深入瞭解)
4、Looper
Looper類的loop會調用MessageQueue類的next方法取出消息,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;
// ...... 省略
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// ...... 省略
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
try {
// 最主要的是這行代碼,調用target的dispatchMessage方法。
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
// ...... 省略
msg.recycleUnchecked(); // 回收消息
}
}
從代碼中看,最重要的是msg調用了target對象的dispatchMessage方法,而這個target即是Handler類型,也即是在sendMessage時賦值的。由此可以知道,消息的發送和處理均是在Handler中處理的。Handler中dispatchMessage方法如下
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
// 如果消息中攜帶有callback參數,callback類型爲Runnable
if (msg.callback != null) {
handleCallback(msg); // 這個方法會直接調用message.callback.run()
} else {
// 如果在構造Handler時傳入了Callback參數,則用此Callback對象處理消息
if (mCallback != null) {
// 如果callback的handleMessage方法返回false,還會繼續用handler自身的方法。
if (mCallback.handleMessage(msg)) {
return;
}
}
// 常見的構造handler時需要覆寫的方法,handler中該方法爲空實現。
handleMessage(msg);
}
}
到這裏,即完成了消息的分發與處理。
5、Message
Message類相當於鏈表中的一個節點,因爲其保存了對下一個節點的引用,即next變量。同時還定義了what,arg1,arg2、data,callback成員變量。獲取Message的方法除了直接new之外,系統還提供了一個obtain方法從緩存池中獲取,代碼如下:
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool; // 此處的sPool指向鏈表的頭節點
sPool = m.next;
m.next = null; // 斷開與頭節點的鏈接
m.flags = 0; // clear in-use flag
sPoolSize--; // 緩存池大小減1
return m;
}
}
// 如果沒有緩存則new一個Message
return new Message();
}
而緩存池的釋放則是在message的recycleUnchecked方法中,如下
/**
* Recycles a Message that may be in-use.
* Used internally by the MessageQueue and Looper when disposing of queued Messages.
*/
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
// 採用頭插法,將回收到的Message放在鏈表的頭部
// 當前節點的next鏈接到原鏈表頭部。
next = sPool;
// 將當前節點作爲新頭節點
sPool = this;
// 緩存池大小加1
sPoolSize++;
}
}
}
6、IdleHandler
在MessageQueue的next方法中,如果消息爲空的情況下,會調用IdleHandler對象的queueIdle方法,而Idlehandler對象可以通過MessageQueue暴露的接口添加多個。使用方法如下:
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
Log.i("test", "idleHandler queueIdle " + System.currentTimeMillis());
// 返回true時,會保持該IdleHandler一直活躍。false則會移除該IdleHandler。
return true;
}
});
該方法的註釋說明了調用的場景
/**
* 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.
*/
我們可以通過此方法將一些操作在沒有消息時處理,從而避免搶佔資源優化交互體驗。
7、總結
1、構造Handler時,會通過ThreadLocal獲取Looper對象及其MessageQueue對象。Handler發送消息時會調用MessageQueue的enqueueMessage方法將消息加入到鏈表中去。同時,Looper的loop循環會調用MessageQueue的next方法取出一個消息,調用該消息的dispatchMessage方法,從而將消息分發出去。
2、其中,MessageQueue的next方法中的nativePollOnce會阻塞住線程,而enqueueMessage中會調用nativeWake喚醒線程。
3、主線程中的Looper會阻塞住主線程。由於線程的特性,線程執行完成後就會退出,故ActivityThread類中的Loop.loop方法的無限循環會阻塞住主線程,從而無法走到最後一行的拋出異常。在主線程阻塞沒有引起ANR的原因是發生阻塞時沒有事件發生,而當有事件發生時,系統會喚醒線程從而避免了ANR。即有事件時喚醒線程處理,無事件時阻塞等待。
4、在一個handler對象上removeMessage時需要注意的是,只能該handler對象的messgae。當出現message不能刪除的時候,要檢查一下該message是不是關聯到當前的handler對象。
8、參考
以下兩篇文章寫的非常好。
1、MessageQueue
2、Android中線程間通信機制Handler疑問解答