以前在學習Hanlder源碼的時候,恰好涉及到 Looper.loop 方面的知識,這裏進行一下回答java
首先,在ActivityThread.main 方法中,能夠找到Looper相關的初始化代碼,在這段代碼裏面作了兩件事, 一、初始化當前線程的Looper
二、開啓循環bash
public static void main(String[] args) {
//省略掉部分不相關代碼
//..........
//prepareMainLooper 方法在當前線程初始化了一個消息隊列不容許退出Looper
Looper.prepareMainLooper();
//..........
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
複製代碼
進入loop方法oop
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) {
// No message indicates that the message queue is quitting.
return;
}
//省略部分不相關的代碼
//..................
try {
//三、執行隊列中的消息
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
//省略部分不相關的代碼
//..................
msg.recycleUnchecked();
}
複製代碼
loop()方法中,代碼很是簡單,去除掉一些無用的日誌打印和不相關的代碼,剩餘的就很是簡單了,分三步走
一、獲取到looper中的 MessageQueue
二、開啓一個死循環,從MessageQueue 中不斷的取出消息
三、執行取出來的消息 msg.target.dispatchMessage(msg);(順便說一下,Handler的handleMessage()方法就是在這一步執行的,有興趣的能夠本身看看,這裏就不細說了)學習
在第二步裏面,會發生阻塞,若是消息隊列裏面沒有消息了,會無限制的阻塞下去,主線程休眠,釋放CPU資源,直到有消息進入消息隊列,喚醒線程。從這裏就能夠看出來,loop死循環自己大部分時間都處於休眠狀態,並不會佔用太多的資源,真正會形成線程阻塞的反而是在第三步裏的 msg.target.dispatchMessage(msg)方法,所以若是在生命週期或者handler的Handler的handleMessage執行耗時操做的話,纔會真正的阻塞UI線程;ui
到這裏,已經從java層解釋了Looper.loop爲何不會阻塞掉UI線程;最後,再看一下queue.next()方法,畢竟代碼留個尾巴不看實在太憋屈了this
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方法的時候,若是有當前消息能夠被馬上執行,就會直接返回,若是消息須要延遲執行,則會接着阻塞一段時間,到消息能夠被執行的時候再繼續線程執行消息,若是隊列裏面沒消息了,就會無限期的阻塞下去,直到新的消息進入隊列喚醒線程;spa
至於何時喚醒阻塞,就須要看看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層對問題進行了解答,時間倉促,可能有不完善的地方,對於有錯誤的地方,歡迎指正,共同窗習進步