版權聲明:轉載前請留言獲得作者許可,轉載後标明作者 張拭心 與 原文連結。大家都是成年人,創作不易,感謝您的支援! https://blog.csdn.net/u011240877/article/details/72892321
- 不要心急,一點一點的進步才是最靠譜的。
讀完本文你将了解:
-
- 前言
- Message
- 如何擷取一個消息
- Message.obtain()
- 消息的回收利用
- MessageQueue
- MessageQueue 的屬性
- 何時初始化
- 消息入隊的過程
- 消息出隊的過程
- Looper
- 線程相關 ThreadLocal
- 無限循環排程
- 如何停止
- Handler
- Handler 的屬性
- 發送消息
- 處理消息
- 移除消息
- 主線程消息機制
- 總結
- Thanks
前言
本來我以為自己很了解 Handler,在印象中 Android 消息機制無非就是:
- Handler 給 MessageQueue 添加消息
- 然後 Looper 無限循環讀取消息
- 再調用 Handler 處理消息
整體的流程有了,但是一直沒有結合源碼捋一捋。
直到有一天在使用 Handler 發送消息時遇到了一個問題:
This message is already in use.
這才去翻了翻源碼,今天總結一下。
Android 消息機制主要涉及 4 個類:
- Message
- MessageQueue
- Handler
- Looper
我們依次結合源碼分析一下。
Message
“消息機制”,其中要傳遞的就是 Message,官方對它的描述是:
包含任意類型的對象和描述資訊,可以被發送給 Handler。
Message
的主要屬性如下:
//用來辨別一個消息,接收消息方可以根據它知道這個消息是做什麼的
public int what;
//如果你的消息要傳遞的資料是整型的,可以直接使用 arg1 和 arg2,而不需要使用構造一個 Bundle
public int arg1;
public int arg2;
//一個任意類型的對象,在使用 Messenger 跨程序傳遞消息時,通常使用它傳遞給接收者
//在其他場景下我們一般使用 setData() 方法
public Object obj;
//負責回複消息的 Messenger,有的場景下(比如接受者、發送者模型)需要使用它
public Messenger replyTo;
//目前消息的标志,隻在被 Messenger 傳遞消息時使用,其他情況下都是 -1
public int sendingUid = -;
//辨別目前消息是否在被使用
//當一個消息入隊時這個标志會被改變,在被重新擷取後重置
//當一個消息已經在被使用時,二次入隊或者回收會報錯(這就是我前言中提到的錯誤原因)
/*package*/static final int FLAG_IN_USE = << ;
//辨別目前 消息是否是異步的
/*package*/static final int FLAG_ASYNCHRONOUS = << ;
//在 copyFrom 方法中要清除的标志
/*package*/static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;
/*package*/int flags;
/*package*/long when;
//很關鍵的資料部分
/*package*/Bundle data;
//發送和處理消息關聯的 Handler
/*package*/Handler target;
//消息的回調
/*package*/Runnable callback;
//在有些場景下還會以連結清單的形式關聯後一個消息
/*package*/Message next;
//消息池
private static final Object sPoolSync = new Object();
private static Message sPool; //回收消息連結清單
private static int sPoolSize = ;
private static final int MAX_POOL_SIZE = ;
private static boolean gCheckRecycle = true;
Pasted from: http://book2s.com/java/src/package/android/os/message.html#a940ebe121994bfb7f9629bca79beab6
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
可以看到,Message 中比較關鍵的屬性有:
- 辨別幹什麼的 what
- 兩個簡易的整型資料存儲對象 arg1 arg2
- 存儲複雜點的對象 Bundle
- 跨程序通信綁定資料的 object
- 與之關聯的 Handler
- 還有一些标志位,和消息池什麼的
如何擷取一個消息
消息機制最開始肯定需要建構一個消息。
雖然 Message 的構造函數是
public
的,但是官方還是建議我們使用以下 2 種方式擷取一個 Message 對象:
-
Message.obtain()
-
Handler.obtainMessage()
原因是這兩個方法會從一個消息回收池裡擷取消息,而不是建立一個,這樣可以節省記憶體。
Handler.obtainMessage()
也是調用的
Message.obtain()
:
public final Message obtainMessage() {
return Message.obtain(this);
}
- 1
- 2
- 3
去看一下
Message.obtain()
源碼。
Message.obtain()
Message.obtain()
有 7 個重載方法,基本就是在擷取一個 Message 對象的同時直接指派:
我們選最複雜的一個看下源碼:
public static Message obtain(Handler h, int what, int arg1, int arg2,
Object obj) {
Message m = obtain();
m.target = h;
m.what = what;
m.arg1 = arg1;
m.arg2 = arg2;
m.obj = obj;
return m;
}
Pasted from: http://book2s.com/java/src/package/android/os/message.html#50de1a696587dcd5ae3c91eb63985af2
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
的确就是在
obtainI()
的基礎上加了一些指派。
Message.obtain()
源碼如下:
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = ; // 擷取一個複用的消息時,會重置标志位,之前它是 FLAG_IN_USE
sPoolSize--;
return m;
}
}
return new Message();
}
Pasted from: http://book2s.com/java/src/package/android/os/message.html#62dadbbac34056e7ad4fad36d757d6ef
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
可以看到,這個方法會擷取前面提到的
private static Message sPool;
,如果
sPool
存在就從複用消息連結清單頭部取一個消息,然後重置它的标志位;如果不存在複用消息連結清單就建立一個消息。
那什麼時候往消息池中添加消息呢?
消息的回收利用
消息的回收在
Message.recyclerUnchecked()
方法:
void recycleUnchecked() {
flags = FLAG_IN_USE; //目前消息被回收時,會标志為 FLAG_IN_USE
what = ;
arg1 = ;
arg2 = ;
obj = null;
replyTo = null;
sendingUid = -;
when = ;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
可以看到
recycleUnchecked
方法将目前 Message 标志為 FLAG_IN_USE,這樣如果這個方法在被入隊,就會報錯。此外它還清除了其他資料,然後把這個消息加入了回收消息的連結清單中。
那
recycleUnchecked()
什麼時候會被調用呢?
在
MessageQueue
和
Looper
中都有。
MessageQueue.removeMessages()
方法:
void removeMessages(Handler h, int what, Object object) {
if (h == null) {
return;
}
synchronized (this) {
Message p = mMessages;
// Remove all messages at front.
while (p != null && p.target == h && p.what == what
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
p.recycleUnchecked(); //這裡調用了
p = n;
}
// Remove all messages after front.
while (p != null) {
Message n = p.next;
if (n != null) {
if (n.target == h && n.what == what
&& (object == null || n.obj == object)) {
Message nn = n.next;
n.recycleUnchecked(); //這裡也調用了
p.next = nn;
continue;
}
}
p = n;
}
}
}
Pasted from: http://book2s.com/java/src/package/android/os/messagequeue.html#0dbba2b975383e8b440199e5d69c73de
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
Looper.loop()
方法:
public static void loop() {
//...
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
//...
msg.recycleUnchecked();
}
}
Pasted from: http://book2s.com/java/src/package/android/os/looper.html
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
至此我們可以看到,一個消息在被 Looper 處理時或者移出隊列時會被辨別為
FLAG_IN_USE
,然後會被加入回收的消息連結清單,這樣我們調用
Message.obtain()
方法時就可以從回收的消息池中擷取一個舊的消息,進而節約成本。
MessageQueue
MessageQueue
管理着一個
Message
的清單,
Handlers
為它添加消息,
Looper
從中取消息。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICdzFWRoRXdvN1LclHdpZXYyd2LcBzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CX90zdONzZE1EMJRVT4FEVkZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39jMwUzN1cTNzIjNwYDM3EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
MessageQueue
的屬性
MessageQueue
// 隊列是否可以退出
private final boolean mQuitAllowed;
@SuppressWarnings("unused")
private long mPtr; //底層使用的 code
//消息連結清單的開頭
Message mMessages;
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
private IdleHandler[] mPendingIdleHandlers;
private boolean mQuitting;
//指出擷取下一個消息的方法 next() 是否阻塞
private boolean mBlocked;
// Barriers are indicated by messages with a null target whose arg1 field carries the token.
//後一個屏障的 token
private int mNextBarrierToken;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
可以看到,
MessageQueue
雖然叫“消息隊列”,持有的其實是一個消息連結清單的節點。
何時初始化
MessageQueue
一般不直接通路,都是通過
Looper.myQueue()
方法擷取一個消息隊列。
消息隊列在 Looper 的構造函數中初始化:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
MessageQueue 的構造函數中傳入一個是否允許中途退出的标志,然後調用 Native 方法初始化。
這篇文章暫不研究 Native 層源碼。
消息入隊的過程
消息入隊的方法是
enqueueMessage()
方法:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) { //這裡要求消息必須跟 Handler 關聯
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;
//添加消息到連結清單中
if (p == null || when == || when < p.when) {
//之前是空連結清單的時候讀取消息會阻塞,新添加消息後喚醒
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//插入消息到隊列時,隻有在隊列頭部有個屏障并且目前消息是異步的時才需要喚醒隊列
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;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
Message.enqueueMessage()
方法中會檢查入隊的消息是否在被使用,如果是的話會報錯:
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
- 1
- 2
- 3
現在我們清楚了,文章開頭我遇到的:
This message is already in use.
是因為我二次使用了已經在使用的消息,在入隊時 MessageQueue 檢查發現後報的錯。
是以每次調用
Handler.sendMessage()
時,都必須是
obtain()
或者 new 一個新的 Message 對象才行。
此外,如果消息隊列已經退出,還添加消息入隊就會報錯。
消息出隊的過程
消息出隊的方法是
MessageQueue.next()
方法:
Message next() {
//如果消息的 looper 退出,就退出這個方法
final long ptr = mPtr;
if (ptr == ) {
return null;
}
int pendingIdleHandlerCount = -; // -1 only during first iteration
int nextPollTimeoutMillis = ;
//也是一個循環,有合适的消息就傳回,沒有就阻塞
for (;;) {
if (nextPollTimeoutMillis != ) { //如果有需要過段時間再處理的消息,先調用 Binder 的這個方法
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
//擷取下一個消息
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages; //目前連結清單的頭結點
if (msg != null && msg.target == null) {
//如果消息沒有 target,那它就是一個屏障,需要一直往後周遊找到第一個異步的消息
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 {
// 消息是正常的、可以立即處理的
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 = -;
}
//如果收到退出的消息,并且所有等待處理的消息都處理完時,調用 Native 方法銷毀隊列
if (mQuitting) {
dispose();
return null;
}
//有消息等待過段時間執行時,pendingIdleHandlerCount 增加
if (pendingIdleHandlerCount <
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= ) {
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, )];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
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 = ;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
可以看到,
MessageQueue.next()
方法裡有一個循環,在這個循環中周遊消息連結清單,找到下一個可以處理的、target 不為空的消息并且執行時間不在未來的消息,就傳回,否則就繼續往後找。
如果有阻塞(沒有消息了或者隻有 Delay 的消息),會把 mBlocked這個變量标記為 true,在下一個 Message 進隊時會判斷這個message 的位置,如果在隊首就會調用 nativeWake() 方法喚醒線程!
其中看到個
IdleHandler
是什麼呢?
public static interface IdleHandler {
//當消息隊列沒有消息時會回調這個方法,阻塞等待有消息進入
//傳回 true 的話表示喚醒阻塞的線程,false 表示移除
//如果消息隊列中有消息等待在将來執行,也會調用這個方法
boolean queueIdle();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
根據源碼和注釋我們可以知道
IdleHandler
是一個線程阻塞時回調的接口。
MessageQueue 中提供了監聽阻塞回調的注冊和移除接口:
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}
public void removeIdleHandler(@NonNull IdleHandler handler) {
synchronized (this) {
mIdleHandlers.remove(handler);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
當消息隊列阻塞時,會回調這些監聽阻塞的觀察者,告訴他們:我有空了!來找我玩啊!
在
queueIdle()
中做的操作,它會在 handler 空閑時被調用,可以充分利用 handler,我們可以在代碼裡這樣使用:
Handler handler = new Handler();
try {
Field field = Looper.class.getDeclaredField("mQueue");
field.setAccessible(true);
MessageQueue queue = (MessageQueue) field.get(handler.getLooper());
queue.addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
//這裡做一些操作
return true;
}
});
} catch (Exception e) {
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
這裡隻簡單介紹了 Java 層的 MessageQueue,關于 Native 層的可以看這篇文章:http://blog.csdn.net/innost/article/details/47317823
Looper
前面介紹了 Android 消息機制中消息和消息隊列,有了傳遞的消息和存儲的隊列,接下來我們結合源碼了解下進行排程的 Looper。
官方文檔對 Looper 的介紹:
Looper 是用于運作一個線程中的消息的類。
Looper 的屬性很簡單:
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // 主線程中的 Looepr
final MessageQueue mQueue; //與之管理的消息隊列
final Thread mThread; //所在的線程
private Printer mLogging;
private long mTraceTag;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
線程中預設沒有 Looper,我們需要調用
Looper.prepare()
方法為目前線程建立一個 Looper,然後就可以調用
loop()
方法排程消息。
樣例代碼如下:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
線程相關 ThreadLocal
前面講了在一個線程中需要調用
Looper.prepare()
方法建立一個 Looper:
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));
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
可以看到,一個線程中隻能有一個 Looper。
建立一個或者或許目前線程的 Looper 都通過
ThreadLocal
,我們來了解下它的主要源碼。
ThreadLocal.get()
和
ThreadLocal.set()
源碼:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
ThreadLocalMap
中持有一個
Entry
的數組:
private Entry[] table;
static class Entry extends WeakReference<ThreadLocal> {
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
可以看到,
ThreadLocal
先通過目前線程擷取
ThreadLocalMap
,然後在
ThreadLocalMap
中儲存
ThreadLocal
和 資料的關聯。
也就是一個類似 Map
無限循環排程
線上程中建立一個
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) { //目前線程必須建立 Looper 才可以執行
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
//底層對 IPC 辨別的處理,不用關心
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) { //無限循環模式
Message msg = queue.next(); //從消息隊列中讀取消息,可能會阻塞
if (msg == null) { //當消息隊列中沒有消息時就會傳回,不過這隻發生在 queue 退出的時候
return;
}
//...
try {
msg.target.dispatchMessage(msg); //調用消息關聯的 Handler 處理消息
} finally {
if (traceTag != ) {
Trace.traceEnd(traceTag);
}
}
//...
msg.recycleUnchecked(); //标記這個消息被回收
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
可以看到,
Looper.loop()
也很簡單,就是調用
MessageQueue.next()
方法取消息,如果沒有消息的話會阻塞,直到有新的消息進入或者消息隊列退出。
拿到消息後調用消息關聯的 Handler 處理消息。
可以看到,Looper 并沒有執行消息,真正執行消息的還是添加消息到隊列中的那個 Handler,真應了那句:解鈴還須系鈴人啊!
如何停止
loop()
源碼中的注釋就提醒我們,開啟循環排程消息後不要忘記調用
quit()
方法結束循環。
Looper.quit() 和 quitSafely ()
源碼:
public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
兩種退出方式都調用的是
MessageQueue.quit(boolean)
方法:
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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
可以看到 Looper 兩種結束方式的差別:
-
:立即把消息連結清單中的所有消息都回收了,比較強硬quit()
- 在停止後如果 Handler 還發送消息,會傳回 false,表示入隊失敗
- 這個方法是不安全的,一般建議使用下面那個
-
:比上面那個溫柔一點quitSafely()
- 隻會将還未執行的消息回收掉
- 在調用之後添加進入的消息不會被處理,Handler.sendMessage 也會傳回 false
當消息隊列被标記位退出狀态時,它的
next()
方法會傳回 null,于是
Looper.loop()
循環就結束了。
Handler
現在我們了解了消息隊列和 Looper,接着就該介紹消息機制中最關鍵的角色 – Handler。
每一個 Handler 都和一個線程的 Looper 以及這個線程中的消息隊列關聯。
Handler 所做的就是 “線程切換”:
- 在子線程将 Message 或者 Runnable 發送到 MessageQueue 中
- 然後等待 Looper 排程這個消息後,再召喚 Handler 來處理消息
- 這時消息已經在建立 Handler 的線程了
這個“線程切換” 是怎麼實作的呢?我們一步步揭曉。
Handler 的屬性
Handler 的屬性如下:
final Looper mLooper;
final MessageQueue mQueue;
final Callback mCallback;
final boolean mAsynchronous;
IMessenger mMessenger;
- 1
- 2
- 3
- 4
- 5
可以看到 Handler 的屬性很簡單,其中 mCallback 可以作為構造函數的參數用于建立 Handler。
public Handler(Callback callback) {
this(callback, false);
}
public interface Callback {
public boolean handleMessage(Message msg);
}
- 1
- 2
- 3
- 4
- 5
- 6
我們平時建立 Handler 都是建立一個 Handler 的子類然後重寫它的 handleMessage 方法,有了
Handler.Callback
接口我們可以用這種方式建立 Handler:
Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
//這裡處理消息
return false;
}
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
最終效果和建立 Handler 子類一樣,但是需要注意,這裡建立了匿名内部類,還是會持有外部引用,導緻記憶體洩漏。
發送消息
Handler 發送的主要有兩種類型:
- Message
- Runnable
發送方法有
postXXX()
和
sendXXX()
兩種,postXXX 發送的是 Runnable,調用的也是 sendXXX,而 Runnable 也會被轉成 Message:
public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), );
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
是以我們直接看 sendXXX 方法:
public final boolean sendMessage(Message msg){
return sendMessageDelayed(msg, );
}
public final boolean sendMessageDelayed(Message msg, long delayMillis){
if (delayMillis < ) {
delayMillis = ;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
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);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
可以看到 Handler 發送消息最後還是調用了消息隊列的
enqueueMessage()
方法。
而我們常用的使用
Handler.sendMessageDelayed()
發送延遲消息,最後其實是在入隊時指定這個 msg.when,在
MessageQueue.next()
方法中,會對 msg.when > now 的消息設定延遲處理,具體實作是在 Native 層。
消息入隊後,Looper 如果啟動了就可以從隊列裡循環取消息,然後調用
msg.target.dispatchMessage(msg)``` 也就是
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);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
public void handleMessage(Message msg) {
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
可以看到,Handler 在處理消息時,會有三種情況:
- msg.callback 不為空
- 這在使用
發送消息的時候會發生Handler.postXXX(Runnable)
- 這就直接調用 Runnable 的 run() 方法
- 這在使用
- mCallback 不為空
- 這在我們使用前面介紹的 Handler.Callback 為參數構造 Handler 時會發生
- 那就調用構造函數裡傳入的
方法handleMessage()
- 如果傳回 true,那就不往下走了
- 最後就調用
方法Handler.handleMessage()
- 這是一個空實作,需要我們在 Handler 子類裡重寫
移除消息
最後再看一下如果移除消息。
由于發送時可以發送 Callback 和 Message,是以取消也有兩種:
- removeCallbacks()
- removeMessages()
看一下源碼發現調用的其實就是消息隊列的出隊方法:
public final void removeCallbacks(Runnable r){
mQueue.removeMessages(this, r, null);
}
public final void removeMessages(int what) {
mQueue.removeMessages(this, what, null);
}
- 1
- 2
- 3
- 4
- 5
- 6
主線程消息機制
我們知道,在主線程中建立 Handler 時不用 papare Looper,這是因為在主線程中系統預設建立了 Looper,它在不停地排程分發消息,是以四大元件的排程、我們的輸入事件、繪制請求才能得到處理。
ActivityThread 就是我們說的主線程,而它的
main()
方法就是當主線程的入口:
public static void main(String[] args) {
/...
Process.setArgV0("<pre-initialized>");
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();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
可以看到在主線程的
main()
方法中初始化了一個 Looper,然後調用了
Looper.loop()
方法開始排程消息。
發送和處理主線程中的消息的 Handler 叫做 H:
private class H extends Handler {
public static final int LAUNCH_ACTIVITY = ;
public static final int PAUSE_ACTIVITY = ;
public static final int PAUSE_ACTIVITY_FINISHING= ;
public static final int STOP_ACTIVITY_SHOW = ;
public static final int STOP_ACTIVITY_HIDE = ;
public static final int SHOW_WINDOW = ;
public static final int HIDE_WINDOW = ;
public static final int RESUME_ACTIVITY = ;
public static final int SEND_RESULT = ;
public static final int DESTROY_ACTIVITY = ;
public static final int BIND_APPLICATION = ;
public static final int EXIT_APPLICATION = ;
public static final int NEW_INTENT = ;
public static final int RECEIVER = ;
public static final int CREATE_SERVICE = ;
public static final int SERVICE_ARGS = ;
public static final int STOP_SERVICE = ;
//...
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
case RELAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityRestart");
ActivityClientRecord r = (ActivityClientRecord)msg.obj;
handleRelaunchActivity(r);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
//...
}
if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
H 的代碼很多,我們隻節選一部分。從上述代碼可以看到,H 内部定義了消息類型,然後根據消息的類型進行不同的處理。
主線程的消息機制如下:
- ActivityThread 通過 ApplicationThread 與 AMS 進行程序通信
- AMS 以程序通信的方式完成 ActviityThread 的請求後回調 ApplicationThread 的 Binder
- 然後 ApplicationThread 向 H 發送消息,H 将消息切換到主線程,然後進行處理
總結
這篇文章結合源碼完整的看了一遍 Message MessageQueue Handler Looper,現在看着上面的圖,可以自信地說我“熟悉 Android 消息機制”了哈哈。
結合 Android 性能優化:多線程 了解會更深一些!
Thanks
《Android 開發藝術探索》
http://book2s.com/java/src/package/android/os/handler.html#d0bdd75e69340e6c9e876ee32501beae
http://book2s.com/java/src/package/android/os/looper.html
http://book2s.com/java/src/package/android/os/message.html