從原始碼的角度解析Handler、Looper、Message和MessageQueue
導語
雖然很基礎的一個東西,然是最近面試中還是常常最被問到,而且都是到原始碼層,因此決定再造一次輪子!
作為一名Android程式猿,想必在最開始都碰到這麼一個問題,就是在子執行緒中彈出一個 Toast
,會丟擲以下的異常:
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler .<init>(Handler.java:121)
at android.widget.Toast$TN.<init>(Toast.java:322)
at android.widget.Toast.<init>(Toast.java:91)
at android.widget.Toast.makeText(Toast.java:238)
at com.example.testapp.MyActivity$MyAsyncTask.doInBackground(MyActivity.java:25)
at com.example.testapp.MyActivity$MyAsyncTask.doInBackground(MyActivity.java:21 )
按傳統的說法就是 Toast
只能在UI執行緒中顯示,實際上不是的,應該是 Toast
只能在帶有 Looper
的執行緒中顯示。
另一個常見的場景,就是在子執行緒建立一個 Handler
也會丟擲與上述一樣的異常:
Java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:121)
而在主執行緒為什麼不會有該異常呢?這就是原始碼的東西了,在程式入口方法已經呼叫過以下程式碼,來建立一個主執行緒了:
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
Looper.loop();
這個時候疑問就來了,什麼是 Looper
?
先來看一張圖,初步瞭解一下 Handler
、Looper
、Message
、MessageQueue
之間的關係
對於一個執行緒來說,Handler
允許傳送和處理與該執行緒的 MessageQueue
相關聯的 Message
或 Runnable
物件。每一個 Handler
例項與單個執行緒的 MessageQueue
相關聯。
當我們建立一個 Handler
例項的時候,它將會繫結到當前所處的執行緒及其對應的訊息佇列,然後就是往訊息佇列裡面傳遞訊息或者 Runabble
物件,並在訊息佇列中取出訊息來處理,或者取出 Runnable
物件進行執行!
Handler
Handler 作用
從本質上來講,Handler 主要有以下兩個作用
- 排程訊息和runnable物件去被執行,換句話說,就是在同一個執行緒中處理一些訊息
- 使得某個訊息動作在不同執行緒中執行,也就是往其他執行緒的訊息佇列裡插入訊息
訊息的排程主要有以下幾種方式:
post(Runnable)
postAtTime(Runnable,long)
postDelayed(Runnable,long)
sendMessage(Message)
sendMessageAtTime(Message,long)
sendMessageDelayed(Message,long)
sendEmptyMessage(int)
sendEmptyMessageAtTime(int, long)
sendEmptyMessageDelayed(int, long)
最後實際上都是呼叫 sendEmptyMessageAtTime(Message,long)
方法
從上面的這些方法中可以看出:
- post開頭的幾個方法,允許將
Runnable
物件插入到訊息佇列以便呼叫。 - sendMessage 對應的幾個方法,可以將
Message
插入到MessageQueue
,然後通過Handler
的handleMessage
方法來處理相應的訊息。
Message
Message 結構
Message
類主要包含以下幾個引數
public int what; // sendEmptyMessage 裡面的 what,在 ```handleMessage``` 方法可以對不同的 Message.what 值做相應處理。
public Object obj; // Message 可以攜帶一個物件
Handler target; // 處理該訊息的Handler
Message next;
Runnable callback; // 訊息處理動作
1、從next引數可知,訊息佇列實際上是一個連結串列結構;
2、來看一下 Handler 的 dispatchMessage 方法:
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
從中我們可以知道,如果 Message 有定義 callback,那麼訊息處理會交由callback 去執行,否則,交由 Handler 的 handleMessage 去執行。
Message 建立及傳送
一般傳送一條訊息,我們會呼叫一下程式碼:
handler.obtainMessage(int what, Object obj).sendToTarget();
那麼,我們就簡單分析一下訊息建立的流程
1、Handler.obtainMessage
public final Message obtainMessage(int what, Object obj)
{
return Message.obtain(this, what, obj);
}
2、 Message.obtain 建立訊息
public static Message obtain(Handler h, int what, Object obj) {
Message m = obtain();
m.target = h; // 指定了處理訊息的Handler
m.what = what;
m.obj = obj;
return m;
}
3、 Message.sendToTarget 傳送訊息
public void sendToTarget() {
target.sendMessage(this); // 呼叫 Handler的sendMessage
}
4、Handler.sendMessage(Message) 傳送訊息,最後實際上是呼叫sendMessageAtTime方法,往MessageQueue裡面插入一條訊息
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);
}
至此,訊息就傳送完畢,也就是插入到了訊息佇列裡面,接下來就是由訊息佇列處理了。
MessageQueue
MessageQueue 結構
private final boolean mQuitAllowed; // 是否允許MessageQueue退出;
private long mPtr; // MessageQueue 是通過呼叫 C++ native MessageQueue 實現的,這個 mPtr 就是指向 native MessageQueue;
Message mMessages; // 表示儲存訊息連結串列的 Head
private boolean mQuitting; // 當前MessageQueue是否正在終止;
private boolean mBlocked; // 表示next()方法呼叫是否被block在timeout不為0的pollOnce上;
MessageQueue
主要包含兩個操作:插入和讀取。讀取操作本身會伴隨著刪除操作,插入和讀取對應的方法分別是 enqueueMessage()
和 next()
。
插入訊息
boolean enqueueMessage(Message msg, long when) {
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;
boolean needWake;
// 插入到連結串列的頭部,條件:連結串列為null或者當前訊息的對應的觸發時間比連結串列頭的觸發時間小,也就是比連結串列頭早執行
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// 通過觸發時間,將訊息插入到佇列中合適的位置
// 如果需要喚醒執行緒處理則呼叫C++中的nativeWake()函式.
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;
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
讀取訊息
訊息迴圈讀取,是在 Looper.loop()
方法呼叫之後,最後來執行 MessageQueue.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();
}
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) {
// 過濾掉同步訊息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// 還未達到下一條訊息的觸發時間,為下一條待處理的訊息設定就緒時間
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;
}
// 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;
}
}
通過上面原始碼可知:
首先會去判斷handler是否為null,是的話就跳過所有的同步訊息,查詢到需要最先處理的非同步訊息。如果第一個待處理的訊息還沒有到要觸發時間,則設定啟用等待時間;否則這個訊息就是需要處理的訊息,將該訊息設定為 inuse狀態,並將佇列設定為非 blocked 狀態,然後返回該訊息。
next()
方法是一個無限迴圈的方法,如果訊息佇列中沒有訊息,那麼 next()
方法會一直阻塞,當有新訊息到來時,next()
會將這條訊息返回同時也將這條訊息從連結串列中移除。
Looper
首先,在理解 Looper
之前,我們需要稍微瞭解一下 ThreadLocal
這個類。
ThreadLocal
是用於為每個執行緒建立一個單獨的變數副本,提供了保持物件的方法和避免參數傳遞的複雜性。ThreadLocal
類有一個泛型引數,設定了儲存到 ThreadLocal
容器中的資料型別。
實際上在 ThreadLocal
類中有一個靜態內部類 ThreadLocalMap
(其類似於Map),用鍵值對的形式儲存每一個執行緒的變數副本,ThreadLocalMap
中元素的key為當前 ThreadLocal
物件,而value對應執行緒的變數副本,每個執行緒可能存在多個 ThreadLocal
。
那麼,在 Looper
中,也儲存該著為每個執行緒單獨建立的 ThreadLocal
,裡面儲存著該執行緒對應的 Looper
。
Looper 建立
我們來看一下 Looper.prepare()
方法:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
// 這也就意味著 prepare 方法,建立了當前執行緒的一個 Looper,並且每個執行緒 只能建立一次
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
Looper 開啟迴圈
來看一下 Looper.loop()
方法:
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the 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;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
// 從佇列中取出一條訊息
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
// 分發訊息給 Handler 處理
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// 回收釋放
msg.recycleUnchecked();
}
}
執行緒阻塞問題
細心的童鞋就會有個疑問,主執行緒對應的這個 Looper
,在呼叫 Looper.loop()
方法之後,開啟了無限死迴圈,那麼為什麼不會造成執行緒阻塞,導致 UI 動不了?
這個問題實際上就需要了解一下 Activity
的啟動過程了。這裡就不細說了,主要先了解一下 ActivityThread.H
這個類,它是繼承於 Handler
,我們可以看一下他的訊息處理:
private final class H extends Handler {
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + msg.what);
switch (msg.what) {
case LAUNCH_ACTIVITY: {
ActivityClientRecord r = (ActivityClientRecord)msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo);
handleLaunchActivity(r, null);
} break;
case RELAUNCH_ACTIVITY: {
ActivityClientRecord r = (ActivityClientRecord)msg.obj;
handleRelaunchActivity(r, msg.arg1);
} break;
case PAUSE_ACTIVITY:
handlePauseActivity((IBinder)msg.obj, false, msg.arg1 != 0, msg.arg2);
maybeSnapshot();
break;
case PAUSE_ACTIVITY_FINISHING:
handlePauseActivity((IBinder)msg.obj, true, msg.arg1 != 0, msg.arg2);
break;
case STOP_ACTIVITY_SHOW:
handleStopActivity((IBinder)msg.obj, true, msg.arg2);
break;
case STOP_ACTIVITY_HIDE:
handleStopActivity((IBinder)msg.obj, false, msg.arg2);
break;
case SHOW_WINDOW:
handleWindowVisibility((IBinder)msg.obj, true);
break;
case HIDE_WINDOW:
handleWindowVisibility((IBinder)msg.obj, false);
break;
case RESUME_ACTIVITY:
handleResumeActivity((IBinder)msg.obj, true,
msg.arg1 != 0);
break;
case SEND_RESULT:
handleSendResult((ResultData)msg.obj);
break;
case DESTROY_ACTIVITY:
handleDestroyActivity((IBinder)msg.obj, msg.arg1 != 0,
msg.arg2, false);
break;
case BIND_APPLICATION:
AppBindData data = (AppBindData)msg.obj;
handleBindApplication(data);
break;
case EXIT_APPLICATION:
if (mInitialApplication != null) {
mInitialApplication.onTerminate();
}
Looper.myLooper().quit();
break;
case NEW_INTENT:
handleNewIntent((NewIntentData)msg.obj);
break;
case RECEIVER:
handleReceiver((ReceiverData)msg.obj);
maybeSnapshot();
break;
case CREATE_SERVICE:
handleCreateService((CreateServiceData)msg.obj);
break;
case BIND_SERVICE:
handleBindService((BindServiceData)msg.obj);
break;
case UNBIND_SERVICE:
handleUnbindService((BindServiceData)msg.obj);
break;
case SERVICE_ARGS:
handleServiceArgs((ServiceArgsData)msg.obj);
break;
case STOP_SERVICE:
handleStopService((IBinder)msg.obj);
maybeSnapshot();
break;
case REQUEST_THUMBNAIL:
handleRequestThumbnail((IBinder)msg.obj);
break;
case CONFIGURATION_CHANGED:
handleConfigurationChanged((Configuration)msg.obj);
break;
case CLEAN_UP_CONTEXT:
ContextCleanupInfo cci = (ContextCleanupInfo)msg.obj;
cci.context.performFinalCleanup(cci.who, cci.what);
break;
case GC_WHEN_IDLE:
scheduleGcIdler();
break;
case DUMP_SERVICE:
handleDumpService((DumpServiceInfo)msg.obj);
break;
case LOW_MEMORY:
handleLowMemory();
break;
case ACTIVITY_CONFIGURATION_CHANGED:
handleActivityConfigurationChanged((IBinder)msg.obj);
break;
case PROFILER_CONTROL:
handleProfilerControl(msg.arg1 != 0, (ProfilerControlData)msg.obj);
break;
case CREATE_BACKUP_AGENT:
handleCreateBackupAgent((CreateBackupAgentData)msg.obj);
break;
case DESTROY_BACKUP_AGENT:
handleDestroyBackupAgent((CreateBackupAgentData)msg.obj);
break;
case SUICIDE:
Process.killProcess(Process.myPid());
break;
case REMOVE_PROVIDER:
completeRemoveProvider((IContentProvider)msg.obj);
break;
case ENABLE_JIT:
ensureJitEnabled();
break;
case DISPATCH_PACKAGE_BROADCAST:
handleDispatchPackageBroadcast(msg.arg1, (String[])msg.obj);
break;
case SCHEDULE_CRASH:
throw new RemoteServiceException((String)msg.obj);
}
if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + msg.what);
}
}
那麼,對於建立一個Service來說,我們看一下 ApplicationThread 的排程:
public final void scheduleCreateService(IBinder token,
ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
updateProcessState(processState, false);
CreateServiceData s = new CreateServiceData();
s.token = token;
s.info = info;
s.compatInfo = compatInfo;
// 傳送建立 Service 的訊息
sendMessage(H.CREATE_SERVICE, s);
}
不難看出,實際上建立 Service
、包括執行其生命週期,最後都是交由 ActivityThread.H
處理,包括 Activity
的生命週期,也是一樣,所以 Looper
雖然死迴圈,但是本質上我們UI的展示、更新,也是通過 Handler 來處理了,所以並不會造成真正的UI阻塞。
所以,簡單來講,ActivityThread
實際上就是開啟了一個訊息迴圈,來執行 Activity
、Service
等等的相關操作,一旦這個訊息迴圈停止了,則意味著App程序也結束了。
但是,如果 handlerMessage
是在主執行緒執行,其處理儘可能不要執行耗時操作,避免UI卡頓或發生 ANR。
結語
最後,再回過頭去看看前文中的那張圖,相信你就能基本理解了!
感謝閱讀!