Looper在Android消息機制中的主要作用就是一直循環從MessageQueue中取Message,取出Message後交給它的target,該target是一個Handler對象,消息交給Handler後通過調用Handler的dispatchMessage()
方法進行處理。
filed | 含義 | 說明 |
---|---|---|
sThreadLocal | Looper 對象 | 每個線程中的Looper對象其實是一個ThreadLocal,即線程本地存儲(TLS)對象 |
sMainLooper | 主線程使用的Looper對象 | 由系統在ActivityThread主線程中創建。 |
mQueue | 和Looper對應的消息隊列 | 一個Looper依賴一個消息隊列(一對一) |
mThread | 和Looper對應的線程 | 一個Looper和一個線程綁定(一對一) |
為線程創建消息循環
使用Looper對象時,會先調用靜態的prepare方法或者prepareMainLooper方法來創建線程的Looper對象。如果是主線程會調用prepareMainLooper,如果是普通線程只需調用prepare方法,兩者都會調用prepare(boolean quitAllowed)方法,該方法源碼如下:
/**
* 該方法會創建Looper對象,Looper對象的構造方法中會創建一個MessageQueue對象,再將Looper對象保存到當前線程 TLS
* @param quitAllowed
*/
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
// 試圖在有Looper的線程中再次創建Looper將拋出異常,一個線程只能有一個looper。
throw new RuntimeException("Only one Looper may be created per thread");
}
// 我們調用該方法會在調用線程的TLS中創建Looper對象
sThreadLocal.set(new Looper(quitAllowed));
}
第一次調用prepare()方法後,新創建出來的當前線程對應的Looper對象就被存儲到一個
TLS
對象中,如果重複調用,就會報錯。
開啟消息循環
Looper類乃至Android消息處理機制的核心部分,在使用Looper時,調用完Looper.prepare()
後,還需要調用Looper.loop()方法開啟消息循環。該方法是一個死循環會將不斷重複下面的操作,直到沒有消息時退出循環。
把分發後的Message,回收到消息池以複用
/**
* 在這個線程中啟動隊列,請確保在循環結束時候調用{@link #quit()}
*
* 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();//獲取TLS存儲的Looper對象
if (me == null) {//如果沒有調用Loop.prepare()的話,就會拋出下面這個異常
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;//從Looper中取出消息隊列
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
//確保在權限檢查時基於本地進程,而不是基於最初調用進程。
final long ident = Binder.clearCallingIdentity();
//死循環,循環的取消息,沒有新消息就會阻塞
for (;;) {
//調用MessageQueue的next方法來獲取新消息,而,next是一個阻塞方法,沒有消息時,loop方法將跟隨next方法會一直阻塞在這裡。
Message msg = queue.next(); // might block,如果沒有新消息,這裡會被阻塞。
//因為以上獲取消息是阻塞方法,所以,當消息隊列中沒有消息時,將阻塞在上一步。而如果上一步拿到了一個空消息,只能說明
//我們退出了該消息隊列。那麼這裡直接退出
if (msg == null) {
// No message indicates that the message queue is quitting.
//沒有消息意味著消息隊列正在退出。這也就是為什麼Looper的quit()方法中只需要退出消息隊列即可。
return;
}
<span class="hljs-comment">// This must be in a local variable, in case a UI event sets the logger</span>
Printer logging = me.mLogging;<span class="hljs-comment">//默認為null,可通過setMessageLogging()方法來指定輸出,用於debug功能</span>
<span class="hljs-keyword">if</span> (logging != <span class="hljs-keyword">null</span>) {
logging.println(<span class="hljs-string">">>>>> Dispatching to "</span> + msg.target + <span class="hljs-string">" "</span> +
msg.callback + <span class="hljs-string">": "</span> + msg.what);
}
msg.target.dispatchMessage(msg); <span class="hljs-comment">//msg.target就是與此線程關聯的Handler對象,調用它的dispatchMessage處理消息</span>
<span class="hljs-keyword">if</span> (logging != <span class="hljs-keyword">null</span>) {
logging.println(<span class="hljs-string">"<<<<< Finished to "</span> + msg.target + <span class="hljs-string">" "</span> + msg.callback);
}
<span class="hljs-comment">// Make sure that during the course of dispatching the</span>
<span class="hljs-comment">// identity of the thread wasn't corrupted.</span>
<span class="hljs-keyword">final</span> <span class="hljs-keyword">long</span> newIdent = Binder.clearCallingIdentity();<span class="hljs-comment">//確保分發過程中identity不會損壞</span>
<span class="hljs-keyword">if</span> (ident != newIdent) {
Log.wtf(TAG, <span class="hljs-string">"Thread identity changed from 0x"</span>
+ Long.toHexString(ident) + <span class="hljs-string">" to 0x"</span>
+ Long.toHexString(newIdent) + <span class="hljs-string">" while dispatching to "</span>
+ msg.target.getClass().getName() + <span class="hljs-string">" "</span>
+ msg.callback + <span class="hljs-string">" what="</span> + msg.what);
}
msg.recycleUnchecked(); <span class="hljs-comment">//將已經處理過的消息會受到消息池</span>
}
}
上面代碼中可以看到有logging方法,這是用於debug的,默認情況下logging == null,通過設置setMessageLogging()用來開啟debug工作。
獲得消息循環myLooper()
方法用於獲取當前消息循環對象。Looper對象從成員變量 sThreadLocal(線程本地存儲(TLS)對象
) 中獲取。
獲得的Looper對象可以作為Handler的構建函數參數,將在下篇文章中說明。
public void quit() {
mQueue.quit(false);//消息移除
}
public void quitSafely() {
mQueue.quit(true);
}
Looper.loop()
時調用Looper的構造函數(代碼見上文)。在Looper初始化時,新建了一個MessageQueue的對象保存了在成員mQueue中。Looper是依賴於一個線程和一個消息隊列的。 private Looper(boolean quitAllowed) {
// 每個Looper對象中有它的消息隊列,和它所屬的線程
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
</div>