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工作過程

  1. 為線程創建消息循環
    使用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對象中,如果重複調用,就會報錯。

  2. 開啟消息循環
    Looper類乃至Android消息處理機制的核心部分,在使用Looper時,調用完Looper.prepare()後,還需要調用Looper.loop()方法開啟消息循環。該方法是一個死循環會將不斷重複下面的操作,直到沒有消息時退出循環。

    1. 讀取MessageQueue的下一條Message
    2. 把Message分發給相應的target(Handler)來處理
    3. 把分發後的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工作。

  3. 獲得消息循環
    myLooper()方法用於獲取當前消息循環對象。Looper對象從成員變量 sThreadLocal(線程本地存儲(TLS)對象) 中獲取。

    獲得的Looper對象可以作為Handler的構建函數參數,將在下篇文章中說明。

  4. 退出消息循環
    主要是退出消息隊列:
      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()
    該方法只在主線程中調用,系統已幫我們做好,我們一般不用也不能調用。