之前在學習Hanlder源碼的時候,剛好涉及到 Looper.loop 方面的知識,這裏進行一下回答
首先,在ActivityThread.main 方法中,可以找到Looper相關的初始化代碼,在這段代碼裏面做了兩件事, 1、初始化當前線程的Looper
2、開啓循環
public static void main(String[] args) {
//省略掉部分不相關代碼
//..........
//prepareMainLooper 方法在當前線程初始化了一個消息隊列不允許退出Looper
Looper.prepareMainLooper();
//..........
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
進入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.");
}
//1、獲取到消息隊列
final MessageQueue queue = me.mQueue;
//省略部分不相關的代碼
//..................
//開啓死循環
for (;;) {
//2、拿到隊列中的消息
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
//省略部分不相關的代碼
//..................
try {
//3、執行隊列中的消息
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
//省略部分不相關的代碼
//..................
msg.recycleUnchecked();
}
loop()方法中,代碼非常簡單,去除掉一些無用的日誌打印和不相關的代碼,剩餘的就非常簡單了,分三步走
1、獲取到looper中的 MessageQueue
2、開啓一個死循環,從MessageQueue 中不斷的取出消息
3、執行取出來的消息 msg.target.dispatchMessage(msg);(順便說一下,Handler的handleMessage()方法就是在這一步執行的,有興趣的可以自己看看,這裏就不細說了)
在第二步裏面,會發生阻塞,如果消息隊列裏面沒有消息了,會無限制的阻塞下去,主線程休眠,釋放CPU資源,直到有消息進入消息隊列,喚醒線程。從這裏就可以看出來,loop死循環本身大部分時間都處於休眠狀態,並不會佔用太多的資源,真正會造成線程阻塞的反而是在第三步裏的 msg.target.dispatchMessage(msg)方法,因此如果在生命週期或者handler的Handler的handleMessage執行耗時操作的話,纔會真正的阻塞UI線程;
到這裏,已經從java層解釋了Looper.loop爲什麼不會阻塞掉UI線程;最後,再看一下queue.next()方法,畢竟代碼留個尾巴不看實在太憋屈了
Message next() {
// mPtr保存了NativeMessageQueue的指針,調用nativePollOnce進行等待
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 兩個參數,nextPollTimeoutMillis 表示的是等待時間,-1的時候表示無限制等待
//在這裏可以看出,如果消息隊列裏面沒有消息,就會一直等待,直到隊列裏面加入新的消息,喚醒線程
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) {
if (now < msg.when) {
//如果下一條消息執行時間還未到,則計算出剩餘需要阻塞的時間,給nativePollOnce方法,讓他阻塞指定的時間後,繼續執行
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 = -1;
}
//省略部分不相關的代碼
//..................
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
//省略部分不相關的代碼
//..................
}
}
從上面代碼可以看出來,在調用next方法的時候,如果有當前消息可以被立刻執行,就會直接返回,如果消息需要延遲執行,則會接着阻塞一段時間,到消息可以被執行的時候再繼續線程執行消息,如果隊列裏面沒消息了,就會無限期的阻塞下去,直到新的消息進入隊列喚醒線程;
至於什麼時候喚醒阻塞,就需要看看enqueueMessage(Message msg, long when) 方法了,在這個方法裏面,會將新的消息放到消息隊列裏面,並且判斷如果此時線程處於阻塞狀態,就會調用nativeWake()方法喚醒線程,繼續執行next()方法,取出隊列中的消息
最後總結一下:
loop()開啓死循環後,會命令MessageQueue通過 next()方法 取出之前儲存的消息,如果有立刻被拿出來執行msg.target.dispatchMessage(msg);如果此時MessageQueue中已經沒有消息了(大部分時候都沒有),MessageQueue就會無限期的阻塞下去nativePollOnce(ptr, nextPollTimeoutMillis),釋放cpu資源,這時候並不會造成UI線程卡頓,直到有新的消息存入隊列enqueueMessage(Message msg, long when),喚醒之前阻塞的線程 nativeWake(mPtr),繼續執行next()方法;
我只是從java層對問題進行了解答,時間倉促,可能有不完善的地方,對於有錯誤的地方,歡迎指正,共同學習進步