Looper
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工作過程
-
為線程創建消息循環
使用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()方法開啟消息循環。該方法是一個死循環會將不斷重複下面的操作,直到沒有消息時退出循環。- 讀取MessageQueue的下一條Message
- 把Message分發給相應的target(Handler)來處理
-
把分發後的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; } // This must be in a local variable, in case a UI event sets the logger Printer logging = me.mLogging;//默認為null,可通過setMessageLogging()方法來指定輸出,用於debug功能 if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } msg.target.dispatchMessage(msg); //msg.target就是與此線程關聯的Handler對象,調用它的dispatchMessage處理消息 if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } // Make sure that during the course of dispatching the // identity of the thread wasn't corrupted. final long newIdent = Binder.clearCallingIdentity();//確保分發過程中identity不會損壞 if (ident != newIdent) { Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what); } msg.recycleUnchecked(); //將已經處理過的消息會受到消息池 } }
上面代碼中可以看到有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構造方法
Looper在執行靜態方法Looper.loop()
時調用Looper的構造函數(代碼見上文)。在Looper初始化時,新建了一個MessageQueue的對象保存了在成員mQueue中。Looper是依賴於一個線程和一個消息隊列的。private Looper(boolean quitAllowed) { // 每個Looper對象中有它的消息隊列,和它所屬的線程 mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
-
prepareMainLooper()
該方法只在主線程中調用,系統已幫我們做好,我們一般不用也不能調用。