1. 程式人生 > >Android執行緒的Looper相關知識

Android執行緒的Looper相關知識

Android執行緒的Looper,Handler相關知識


Android中的Looper類,是用來封裝訊息迴圈和訊息佇列的一個類,用於在android執行緒中進行訊息處理。Handler其實可以看做是一個工具類,用來向訊息佇列中插入訊息的。

Android官方文件中Looper的介紹: Class used to run a message loop for a thread. Threads by
default do not have a message loop associated with them; to create one, call prepare() in
the thread that is to run the loop, and then loop() to have it process messages until the loop is stopped.
Most interaction with a message loop is through the Handler class. 
This is a typical example of the implementation of a Looper thread, using the separation of prepare() and loop() to create an initial Handler to communicate with the Looper.

Looper實現原理

1. Looper可以理解為一個類似輪詢器
2. Looper在建立的時候,會自動建立一個MessageQueue(訊息佇列)。
3. 將內部執行緒物件指向自動建立的執行緒。
4. 然後當Looper開啟的時候,去不斷遍歷“詢問”訊息佇列,如果沒有訊息,佇列為空,那麼就繼續輪詢
。如果有訊息進入佇列,則對訊息進行處理,回撥handler的handlemessage方法進行處理

Looper建立的流程

  1. Looper類用來為一個執行緒開啟一個訊息迴圈。 預設情況下android中新誕生的執行緒是沒有開啟訊息迴圈的。(主執行緒除外,主執行緒系統會自動為其建立Looper物件,開啟訊息迴圈。) Looper物件通過MessageQueue來存放訊息和事件。一個執行緒只能有一個Looper,對應一個MessageQueue。

  2. 通常是通過Handler物件來與Looper進行互動的。Handler可看做是Looper的一個介面,用來向指定的Looper傳送訊息及定義處理方法。 預設情況下Handler會與其被定義時所線上程的Looper繫結,比如,Handler在主執行緒中定義,那麼它是與主執行緒的Looper繫結。 mainHandler = new Handler() 等價於new Handler(Looper.myLooper()). Looper.myLooper():獲取當前程序的looper物件,類似的 Looper.getMainLooper() 用於獲取主執行緒的Looper物件。

  3. 在非主執行緒中直接new Handler() 會報如下的錯誤:

    E/AndroidRuntime( 6173): Uncaught handler: thread Thread-8 exiting due to uncaught 
    exception E/AndroidRuntime( 6173): Java.lang.RuntimeException: Can't create handler inside 
    thread that has not called Looper.prepare()

    原因是非主執行緒中預設沒有建立Looper物件,需要先呼叫Looper.prepare()啟用Looper。
    Looper.prepare()相關程式碼:

    /**
      * 初始化Looper,呼叫loop()方法開始迴圈,呼叫quit()退出
      * Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    public static void prepare() {
        prepare(true);
    }
    
    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {//一個執行緒只能有一個Looper
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //儲存Looper
        sThreadLocal.set(new Looper(quitAllowed));
    }
    
    /**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     * 初始化主執行緒的Looper,不要呼叫。因為開啟主執行緒的時候系統已經預設開啟Looper了再次呼叫會報異常
     */
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    
    //建構函式
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);//建立訊息佇列
        mThread = Thread.currentThread();//綁定當前執行緒
    }
  4. Looper.loop()讓Looper開始工作,從訊息佇列裡取訊息,處理訊息。

    注意:寫在Looper.loop()之後的程式碼不會被執行,這個函式內部應該是一個迴圈,當呼叫mHandler.getLooper().quit()後,loop才會中止,其後的程式碼才能得以執行。
    Looper.loop()原始碼:

  /**
     * 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();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        // 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 (;;) {
            // 從訊息佇列中獲取新的訊息,當沒有新訊息的時候會在queue.next()方法中進行迴圈遍歷
            //直到有新的訊息或者呼叫Looper.quit()
            Message msg = queue.next(); 
            if (msg == null) {//如果返回的訊息為空就表示已經呼叫MessageQueue.quit();並且已經MessageQueue.dispose()
                // No message indicates that the message queue is quitting.
                //Return here if the message loop has already quit and been disposed.
                return;
            }
            //msg.target 是Handler物件,這裡進行訊息的分發
            msg.target.dispatchMessage(msg);

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            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);
            }
            //Message物件回收
            msg.recycleUnchecked();
        }
    }

Looper.quit()原始碼

 /**
     * Quits the looper.退出(會有訊息沒有處理完畢就退出)
     * <p>
     * Causes the {@link #loop} method to terminate without processing any
     * more messages in the message queue.
     * </p><p>
     * Any attempt to post messages to the queue after the looper is asked to quit will fail.
     * For example, the {@link Handler#sendMessage(Message)} method will return false.
     * </p><p class="note">
     * Using this method may be unsafe because some messages may not be delivered
     * before the looper terminates.  Consider using {@link #quitSafely} instead to ensure
     * that all pending work is completed in an orderly manner.
     * </p>
     *
     * @see #quitSafely
     */
    public void quit() {
        mQueue.quit(false);
    }

    /**
     * Quits the looper safely.安全退出(訊息處理完畢退出)
     * <p>
     * Causes the {@link #loop} method to terminate as soon as all remaining messages
     * in the message queue that are already due to be delivered have been handled.
     * However pending delayed messages with due times in the future will not be
     * delivered before the loop terminates.
     * </p><p>
     * Any attempt to post messages to the queue after the looper is asked to quit will fail.
     * For example, the {@link Handler#sendMessage(Message)} method will return false.
     * </p>
     */
    public void quitSafely() {
        mQueue.quit(true);
    }
  1. 基於以上知識,可實現主執行緒給子執行緒(非主執行緒)傳送訊息。把下面例子中的mHandler宣告成類成員,在主執行緒通過mHandler傳送訊息即可。

    class LooperThread extends Thread  {  
            public Handler mHandler;  
            public void run()   {  
                Looper.prepare();  
                mHandler = new Handler()   {  
                    public void handleMessage(Message msg)   {  
                        // process incoming messages here  
                    }  
                };  
    
                //這裡可以做兩個修改UI的操作
                //1,Toast可在這裡顯示
                //2,Dialog對話方塊可以顯示
                //3,Snackbar可在非UI執行緒中呼叫顯示,不需要Looper.perpare().因為它的Hander呼叫的主執行緒Looper
                Looper.loop();  
                //在呼叫looper.quit()之前是不會被呼叫的
            }
        }