概述
在Android中的多進程、多線程中提過,只有主線程(UI線程)可以更新UI,其他線程不可以,所以一般耗時操作放到子線程。子線程可以通過Handler將相關信息通知到主線程。
Android的消息機制主要是Handler機制。Handler的工作過程,還有兩個重要部分MessageQueue(消息隊列,下面簡稱MQ)和Looper。
由於下面總結中穿插了不少源碼 便於理解,導致篇幅比較長(加代碼有600多行)。所以先大致總結並給出大致目錄,提前了解是否是需要的內容。
大致總結
消息機制的大致流程
- 線程創建Looper(調用Looper.preper()),然后運行Looper.looper()開啟循環。Looper是和線程綁定的,一個線程只能有一個Looper ,一個Looper內部維護一個MQ。
- Handler調用sendMessage()方法發送消息,將消息加入到MQ中(enqueueMessage())。
- Looper死循環,通過MessageQueue.next()取出符合的消息。
- 取出消息后,通過msg.target.dispatchMessage()分發消息。
- Handler對接受到的消息進行具體處理,調用dispatchMessage()。
- 調用Looper的quit()方法終止,即消息隊列退出、looper循環退出。
注意點
- 創建Handler的線程中一定先有Looper對象, 才能創建Handler,否則會拋異常。下面詳解中通過代碼就能看出來。
- 一個線程只能有一個Looper。但可以創建多個Handler
- MQ是Looper內存維護的。
- 主線程在應用啟動后默認創建Looper 且不能退出。
跨線程大致理解
Handler能做到跨線程,主要是Looper及內部的消息隊列。最常見的:程序啟動主線程創建Looper並綁定了消息隊列,在主線程創建Handler,這個Handler與Looper綁定的。在其他線程(任何地方)通過這個Handler發送消息,消息都加入到了主線程Looper內部的消息隊列(消息發送到的MQ是 創建Handler時綁定的Looper內部MQ),當消息被Looper循環取出,自然就回到了主線程
大致目錄
1 Looper、Handler與MQ
1.1 Looper
1.1.1 Looper的創建:prepare()
1.1.2 Looper循環:loop()
1.2 Handler
1.2.1 Handler的創建
1.2.2 Handler發送消息
1.2.3 Handler分派處理:dispatchMessage
1.3 MessageQueue
1.3.1 入隊:enqueueMessage()
1.3.2 next()方法
1.3.3 退出:quit()
2 其他注意點
2.1 Handler一般使用
2.2 消息池
2.3 子線程到主線程的方法
2.4 主線程的Looper
2.5 ANR問題
Looper、Handler與MQ
Looper
Looper是循環器,為一個線程運行消息循環,不斷檢查消息隊列中是否有新的消息。
Looper.prepare()為當前線程創建一個looper,並在其內部維護一個MQ。
Looper.loop()即looper開始工作,運行消息循環。
下面是Looper部分的幾處源碼,有助於理解。
Looper的創建:prepare()
// sThreadLocal.get() will return null unless you've called prepare().
@UnsupportedAppUsage
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
@UnsupportedAppUsage
final MessageQueue mQueue;
final Thread mThread;
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Looper通過prepare()方法創建,主要有以下幾點:
- 默認創建Looper時,設置參數的為true,即消息隊列是能夠退出的。MQ的退出也提供了安全退出和非安全退出兩種方法(在下面MQ部分中詳述)。
- 一個線程只能創建一個Looper,否則會拋出RuntimeException。
- Looper對象保存在ThreadLocal中的。ThreadLocal是一個線程內部的數據存儲類,每個線程都有自己獨立訪問的變量副本。 libcore/ojluni/src/main/java/java/lang/ThreadLocal.java下有相關源碼。
- Looper創建,即創建了一個內部消息隊列mQueue,並綁定了當前線程。
- 主線程(UI線程)創建的Looper是不可退出的,應用啟動后默認創建好了的。(下面有單獨講解)
Looper循環:loop()
Message.java
@UnsupportedAppUsage
/*package*/ Handler target;
@UnsupportedAppUsage
/*package*/ Runnable callback;
Looper.java
public static void loop() {
......
for (;;) {
//不斷取出下一條消息,mgs為null即消息隊列退出,若沒有消息且沒有退出 消息隊列一直阻塞的
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
......
try {
//分派消息,通過Handler處理
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
......
msg.recycleUnchecked();
}
}
public static @Nullable
Looper myLooper() {
return sThreadLocal.get();
}
由於代碼比較長,截取了關鍵代碼,......表示該處有省略的代碼。
loop()是消息循環運行的關鍵,整體把握這里關注兩行代碼:Message msg = queue.next(); 和 msg.target.dispatchMessage(msg);
。這兩個分別在MQ部分和Handler部分有詳述。
- for(;;)死循環,這是關鍵,真正的消息循環。跳出的唯一條件是queue.next()為null,但實際只有Looper執行quit()才能達成這一條件(next()方法MQ中詳述)。
- next()不斷取出下一條消息,mgs為null即消息隊列退出,循環停止。若沒有消息且沒有退出 消息隊列一直阻塞的
- 當next()返回一條消息,Looper調用msg.target.dispatchMessage(msg)進行分派處理。這里的msg.target就是一個Handler,即調用了Handler的dispatchMessage()方法(Handler中詳述)。
Handler
Handler主要包含消息的發送和接收處理。
Handler的創建
@UnsupportedAppUsage
final Looper mLooper;
final MessageQueue mQueue;
@UnsupportedAppUsage
final Callback mCallback;
final boolean mAsynchronous;
/**
* @hide
*/
public Handler(@Nullable 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 " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
注意幾點:
- Handler有好幾種構造方法,一種是指定了Looper,另一種沒指定。所有構造方法都基本確定4個值:Hanlder關聯的Looper;關聯的MQ;關聯的Callback;是否異步。
- 上述是沒指定Looper最終的方法。從中可以看到,沒有Looper就無法創建Handler。
Handler發送消息
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(@NonNull 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(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
- Handler通過post()、postDelayed()、sendMessage()、sendMessageDelayed()等方法發送消息,最終都是走到了上面的sendMessageAtTime()中。
- Handler各種發送方式,最終到sendMessageAtTime()。這個過程,參數uptimeMillis 即 消息發處的絕對時間也就是when。
- post()等發送方法傳入的參數Runnable即消息的callback(msg.callback),在dispatchMessage中可以看到。
- 發送消息,Handler僅是將消息加入消息隊列中(enqueueMessage()在MQ中詳述),這個消息隊列就是Handler在創建時綁定的Looper的內部消息隊列。
這里都是使用SystemClock.uptimeMillis(),簡單說明下SystemClock.uptimeMillis()與System.currentTimeMillis()區別:
System.currentTimeMillis()是1970年1月1日(UTC)到現在的毫秒值。
SystemClock.uptimeMillis()是設備啟動到現在的時間的毫秒值。(不包括深度睡眠)
SystemClock.elapsedRealtime()是設備啟動到現在時間的毫秒值。(包括深度睡眠)
為什么基本都用SystemClock.uptimeMillis()作為時間間隔的獲取方法呢?
System.currentTimeMillis()通過設置設備的時間是可以改變的,這樣設置后 那些計划的執行明顯會發生異常。
Handler分派處理:dispatchMessage
@UnsupportedAppUsage
/*package*/ Handler target;
@UnsupportedAppUsage
/*package*/ Runnable callback;
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
public interface Callback {
/**
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
boolean handleMessage(@NonNull Message msg);
}
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(@NonNull Message msg) {
}
Looper循環通過queue.next()獲取到一條消息,再通過Handler的dispatchMessage()分派處理。
- 若msg.callback不為空,即上述所說(Handler發送消息 部分)的是否傳入了Runnable參數,有則執行Runnable。(Handler發送消息時傳入)
- 如果msg.callback為空,在Handler創建時指定了Callback參數,即實現了handleMessage()的類作為參數,則直接執行handleMessage()。(Handler創建時傳入)
- 若上面兩種回調都不存在,可由handleMessage()處理。(創建的Handler重寫該方法)
MessageQueue
消息隊列MQ。主要列出Looper和Handler中提到的幾個關於MQ的重要過程。
消息隊列是單鏈表實現的,這屬於數據結構,了解的話可以參考數據結構之隊列(Queue)。
入隊:enqueueMessage()
boolean enqueueMessage(Message msg, long when) {
//Handler為空
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;
//隊列是空或者msg比隊列中其他消息要先執行,該msg作為隊首入隊。
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
//加入隊首。p是指向之前隊首的,了解隊列鏈表實現很容易理解
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} 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.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
//for循環,跳出時:p指向null,prev指向隊尾最后一個消息,即msg最后執行。
//或者p指向第一個when大於msg的消息,prev則指向前面一個(最后一個when小於msg的消息)
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
//msg插入到對應的位置
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
Handler發送消息,將消息加入了消息隊列,即上面的enqueueMessage的方法。
這個方法不難理解,可以看添加的中文注釋。
這里主要注意的是消息的處理時間,看入隊邏輯 可以看出消息隊列是按消息處理時間排隊的。
next()方法
@UnsupportedAppUsage
Message next() {
......
for (;;) {
......
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.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
//還沒有到消息處理時間,設置阻塞時間nextPollTimeoutMillis,進入下次循環的時候會調用nativePollOnce(ptr, 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;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
//沒有消息要處理,nextPollTimeoutMillis設置為-1。
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
//消息隊列退出,返回null
if (mQuitting) {
dispose();
return null;
}
......
}
......
}
}
這個方法比較復雜,代碼比較長。上面只截取了部分關鍵代碼,可以看下添加的中文注釋,能夠理解。
注意兩個地方:
- 消息屏障(同步屏障)。可以通過MessageQueue.postSyncBarrier()設置,即msg.target為null。這里相當於異步優先。
- 僅當mQuitting為true時,即消息隊列退出(quit()),next()才返回null。Looper的looper()循環才停止。
最后來看下消息隊列的退出
退出:quit()
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
private void removeAllFutureMessagesLocked() {
final long now = SystemClock.uptimeMillis();
Message p = mMessages;
if (p != null) {
if (p.when > now) {
removeAllMessagesLocked();
} else {
Message n;
for (;;) {
n = p.next;
if (n == null) {
return;
}
if (n.when > now) {
break;
}
p = n;
}
p.next = null;
do {
p = n;
n = p.next;
p.recycleUnchecked();
} while (n != null);
}
}
}
private void removeAllMessagesLocked() {
Message p = mMessages;
while (p != null) {
Message n = p.next;
p.recycleUnchecked();
p = n;
}
mMessages = null;
}
這個也不復雜,簡單關注兩點:
- quit()有安全退出和非安全退出,兩者的差別從上面也能看到。非安全退出removeAllMessagesLocked(),直接退出 清空隊列消息;安全退出removeAllFutureMessagesLocked(),消息處理時間在現在now之后的消息會被直接清空,而在now之前的會繼續保留 由next()繼續獲取處理。
- quit()調用后才有mQuitting = true,這樣next()才會返回null,最終Looper.looper()循環才會停止。
其他注意點
Handler一般使用
Handler使用,一般是子線程進入主線程更新UI。下面是常見的操作。
主要注意Hanler的創建(多種方式的選擇)以及回調的處理,發送消息的方式。
private final String TAG = "HandlerActivity";
private final int MAIN_HANDLER_1 = 1;
private Handler mMainHandler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage( msg );
switch (msg.what) {
case MAIN_HANDLER_1:
//Do something. like Update UI
break;
}
}
};;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate( savedInstanceState );
Log.d( TAG, "onCreate: MainHandler Looper=" + mMainHandler.getLooper() );
SubThread subThread = new SubThread();
subThread.start();
}
private class SubThread extends Thread {
@Override
public void run() {
//Do something
Message message = mMainHandler.obtainMessage();
message.what = MAIN_HANDLER_1;
mMainHandler.sendMessage(message);
}
}
消息池
Message內部保存了一個緩存的消息池,我們可以通過Message.obtain()或者mMainHandler.obtainMessage()從緩存池獲得一個消息對象。避免每次創建Message帶來的資源占用。
Message.obtain()的多種方法以及mMainHandler.obtainMessage()最終都是調用obtain()從消息池中獲取一個消息對象。
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
子線程到主線程的方法
Handler的一個重要作用就是子線程進入主線程更新UI。
Android中的多進程、多線程也提到過2種
Activity.runOnUiThread(Runnable);View.post(Runnable)/View.postDelayed(Runnable, long)。
這2種方法其實就是Handler機制實現的。
Activity.java
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
View.java
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
......
// Transfer all pending runnables.
if (mRunQueue != null) {
mRunQueue.executeActions( info.mHandler );
mRunQueue = null;
}
......
}
HandlerActionQueue.java
public void executeActions(Handler handler) {
synchronized (this) {
final HandlerAction[] actions = mActions;
for (int i = 0, count = mCount; i < count; i++) {
final HandlerAction handlerAction = actions[i];
handler.postDelayed(handlerAction.action, handlerAction.delay);
}
mActions = null;
mCount = 0;
}
}
主線程的Looper
ActivityThread中的main()是主線程的入口。
從下面代碼中可以看出來,應用啟動 主線程默認創建了Looper,它是不可退出的。Looper有單獨保存並獲取主線程Looper的方法。
主線程Looper創建參數為false(prepare(false)),即looper()的循環是不會停止的,當沒有消息時,一直是阻塞的。
Run|Debug
public static void main(String[] args) {
......
Looper.prepareMainLooper();
......
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
/// M: ANR Debug Mechanism
mAnrAppManager.setMessageLogger(Looper.myLooper());
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
Looper.java
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
ANR問題
looper()死循環為什么沒導致ANR?ANR具體什么造成?ANR和Looper有什么關系?
---這篇已經過長,這些問題在ANR部分總結更好。