Android消息處理機制(Handler、Looper、MessageQueue)
概述
Android是基礎消息驅動的,Android系統為每一個程序維護了一個消息隊列( MessageQueue ),我們可以非常簡單的往消息隊列中插入消息( Handler ),主線程會循環的從隊列中取出消息( Looper ),然后交給消息的發送者來執行( Handler ),這樣就實現了通過消息來驅動程序的執行。
這篇文章會詳情分析android的消息處理機制,為了讓理解更加簡單,我們將從日常應用中最常見的情景---子線程中返回了數據,通過handler更新UI開始。
大家都知道在子線程中通過handler來更新UI的步驟吧
- 創建handler
- 用handler發送消息
- 用handler處理消息(收到消息,更新UI)
what? 好像就用handler就完了,說好的Looper、MessageQueue呢?別急,先來看看Handler的創建
public Handler() {
this(null, false);
}
再來看看調用的這個this()
public Handler(Callback callback, boolean async) {
.........
mLooper = Looper.myLooper();
if (mLooper == null) { throw new RuntimeException( "Can't create whitout Looper");}
mQueue = mLooper.mQueue;
.......
}
首先通過 Looper 的靜態方法獲取對應的 looper ,如果為空,直接拋出異常,由此可以看出消息處理確定是需要 looper 的。這里你可能會有疑問,這個looper是怎么來的?
public static @Nullable Looper myLooper() {
return sThreadLocal.get();}
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();
}
原來是通過 ThreadLocal 的 get 方法從 ThreadLocalMap 鏈表中以當前線程作為 key ,取出的 value 就是我們需要的 looper ,這里的ThreadLocal和ThreadLocalMap我們等下再說。
簡單點就是線程中維護了一個HashMap用來存取looper,這里你可能又有疑問了,既然是map表,我們都沒有做存looper的操作,那取出來肯定是空啊,那為什么我們創建handler的時候沒有出現異常呢?
其實,這是因為程序的主線程(UI線程)在啟動的時候,就創建了looper并存入了ThreadLocalMap中。
public static void main(String[] args) {
......
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler(); }
........
Looper.loop();}
Looper
來看下looper是怎么創建的
public static void prepareMainLooper() {
//創建一個looper并存入ThreadlocalMap中
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
//取出創建好的looper
sMainLooper = myLooper();
}
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//存入ThreadLocalMap中
sThreadLocal.set(new Looper(quitAllowed));
}</code></pre>
通過 prepare() 方法創建一個looper,在通過 myLooper() 方法取出來。再來看看Looper的構造
private Looper(boolean quitAllowed) {
//構造一個消息隊列,復制給mQueue變量
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
看到這里是不是有一點眉目了?構造 looper 的同時構造了 MessageQueue ,構造 Handler 的時候獲取當前線程的 looper 同時獲取到了 消息隊列 ,這樣消息機制就聯系起來了。
消息循環
現在消息機制的三大主力(Handler、Looper、MessageQueue)都具備了,那handle是怎么發送消息,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;
.....
for (; ; ) {
Message msg = queue.next(); // might block
if (msg == null) {
return;
}
msg.target.dispatchMessage(msg);
.....
}
}
這個函數就是不斷的從消息隊列 mQueue 中去取下一個要執行的消息,如果消息為空就退出循環 (其實msg永遠不會為空,因為Message.next()也是一個for無限循環,里面判斷如果msg==null,continue繼續下一次循環) ,不為空就調用 target 的 dispatchMessage() 來執行消息, target 是一個 Handler ,也就是發送這個消息的 handler ,在后續消息發送中我們會看到targer是在哪里賦值的。
這個函數最重要的邏輯是 queue.next() ,它是在MessageQueue中實現的,用來取出消息隊列中的消息
Message next() {
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();
}
//這是一個JNI方法,檢查消息隊列,取出消息并復制給mMessages
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
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 {
//否則就返回消息給looper處理
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;
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true; continue;
}
.......
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}</code></pre>
這個函數稍長,我們把它分解來看。
執行下面的JNI函數會檢查隊列中是否有消息, nextPollTimeoutMillis 表示隊列要等待的時間,初始為0,則不需要等待
nativePollOnce(ptr, nextPollTimeoutMillis);
等這個函數完成返回后,看消息隊列中是否有消息
if (msg != null) {
if (now < msg.when) {
//如果當前時間小于消息的執行時間,消息隊列等待
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
//否則就返回消息給looper處理
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;
}
}
如果有消息,并且消息的執行時間大于當前時間, nextPollTimeoutMillis 重新賦值,等下一次 nativePollOnce(ptr, nextPollTimeoutMillis); 執行的時候就要等待到執行時間了。
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
否則,就直接把消息返回,交給looper處理
else {
mMessages = msg.next;
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
如果整個隊列中沒有任何消息,就進入無限等待狀態,
// 如果消息隊列中沒有任何就無限等待,直到下一個消息到來,并喚醒消息隊列
nextPollTimeoutMillis = -1;
這里注意一點,所謂的等待是空閑等待,而不是忙等待。就是說如果應用程序注冊了 IdleHandler 接口來處理一些事情,那么就會先執行這里 IdleHandler ,因為在執行的過程中可能有新的消息加入隊列,所有需要重置等待時長為0,不在等待
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
.......
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;</code></pre>
是不是有點頭暈?這一段邏輯的確是消息機制中最復雜的,沒明白的話可以多看一遍源碼。把這一部分弄懂了,后面消息的發送和處理就比較簡單了
消息發送
發送消息的函數有很多,但是最終都是調用的同一個函數
public final boolean sendMessageDelayed(Message msg, long delayMillis){
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
延時時間+當前時間組合成為這個消息的執行時間,然后調用 enqueueMessage
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
這里把當前 Handler 賦值給 message的target 變量,還記得 looper.loop() 函數里面如果 MessageQueue.next() 返回了消息就交給 msg.target.dispatchMessage() 處理,這個 target 就是發送消息的 handler 。這就解釋了為什么 handler 發送的消息一定會在 handler 中處理。
之后,消息交給了MessageQueue的enqueueMessage()方法處理
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
......
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || 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;
prev.next = msg;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
這個函數會根據消息的執行時間,從小到大的順序把消息插入到消息隊列中。如果消息隊列處于等待狀態的,還需要喚醒消息隊列。喚醒的函數 nativeWake(mPtr); 是一個JNI方法,我們后續再說。現在只要知道喚醒后,消息隊列又開始循環檢查消息就行了。
消息執行
通過上面我們知道了
- handler發送消息
- Message.next()不斷檢測消息
- 檢測到消息后返回給looper.loop()
- 交給msg.target.dispatchMessage()處理消息
我們也知道了這個target就是發送消息的那個handler,再來看看它是怎么處理消息的
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
這個函數就很簡單了,如果構造 handler 的時候有 callback 就執行callback,否則就執行 handleMessage() ,執行我們自己的邏輯。
好了,我們已經分析完 Handler、Looper、MessageQueue 的創建和它們在消息機制中扮演的角色,認真閱讀這篇文章,我相信你會對android的消息機制有一個比較全面的認識。下面我們看看關于消息機制一些比較有意思的問題
既然 Looper.loop()會阻塞主線程,那為什么不會導致ANR?
這個問題可以通過2個方面來說
1.主線程一定也必須要阻塞。android主線程是伴隨著程序的開啟就啟動,退出才結束的。而不管是進程還是線程,對于Linux系統來說都是一段可執行的代碼,如果不阻塞,主線程代碼執行完畢就結束了,那我們的程序就可能用著用著就退出了。所以android主線程一定是阻塞的。
2.至于為什么不會ANR。先看看會導致ANR的情況
造成ANR的原因一般有兩種:
1.當前的事件沒有機會得到處理(即主線程正在處理前一個事件,沒有及時的完成或者looper被某種原因阻塞住了)
2.當前的事件正在處理,但沒有及時完成
開篇我們說過android系統是基于消息驅動的,不管是點擊一個按鈕需要獲得反饋還是activity生命周期,其實都是通過發送一個消息交給對應handler處理的。所以,主線程阻塞( 其實是消息隊列進入等待狀態 ),說明沒有任何事件(觸摸,點擊,生命周期切換)發生,沒有消息需要處理,當然不會ANR
當消息隊列沒有消息了,Looper.loop()中的for()死循環會退出嗎
閱讀這篇文章后,我們知道了,Looper.loop()是肯定不會退出的,不然下一條消息到來了怎么處理呢?沒有消息,它就會阻塞在 Message.next() 里面的 nativePollOnce(ptr, nextPollTimeoutMillis); 本地方法里面,即 nextPollTimeoutMillis==-1
來自:http://www.jianshu.com/p/ea71e5921d83