Android線程的Looper相關(guān)知識

Android線程的Looper,Handler相關(guān)知識


Android中的Looper類,是用來封裝消息循環(huán)和消息隊列的一個類,用于在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實現(xiàn)原理

1. Looper可以理解為一個類似輪詢器
2. Looper在創(chuàng)建的時候,會自動創(chuàng)建一個MessageQueue(消息隊列)。
3. 將內(nèi)部線程對象指向自動創(chuàng)建的線程。
4. 然后當Looper開啟的時候,去不斷遍歷“詢問”消息隊列,如果沒有消息,隊列為空,那么就繼續(xù)輪詢
。如果有消息進入隊列,則對消息進行處理,回調(diào)handler的handlemessage方法進行處理

Looper創(chuàng)建的流程

  1. Looper類用來為一個線程開啟一個消息循環(huán)。 默認情況下android中新誕生的線程是沒有開啟消息循環(huán)的。(主線程除外,主線程系統(tǒng)會自動為其創(chuàng)建Looper對象,開啟消息循環(huán)。) Looper對象通過MessageQueue來存放消息和事件。一個線程只能有一個Looper,對應(yīng)一個MessageQueue。

  2. 通常是通過Handler對象來與Looper進行交互的。Handler可看做是Looper的一個接口,用來向指定的Looper發(fā)送消息及定義處理方法。 默認情況下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()
    

    原因是非主線程中默認沒有創(chuàng)建Looper對象,需要先調(diào)用Looper.prepare()啟用Looper。
    Looper.prepare()相關(guān)代碼:

    /**
      * 初始化Looper,調(diào)用loop()方法開始循環(huán),調(diào)用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,不要調(diào)用。因為開啟主線程的時候系統(tǒng)已經(jīng)默認開啟Looper了再次調(diào)用會報異常
     */
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    
    
    //構(gòu)造函數(shù)
    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);//創(chuàng)建消息隊列
        mThread = Thread.currentThread();//綁定當前線程
    }
    
  4. Looper.loop()讓Looper開始工作,從消息隊列里取消息,處理消息。

    注意:寫在Looper.loop()之后的代碼不會被執(zhí)行,這個函數(shù)內(nèi)部應(yīng)該是一個循環(huán),當調(diào)用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();
       //開始一個死循環(huán)
       for (;;) {
           // 從消息隊列中獲取新的消息,當沒有新消息的時候會在queue.next()方法中進行循環(huán)遍歷
           //直到有新的消息或者調(diào)用Looper.quit()
           Message msg = queue.next(); 
           if (msg == null) {//如果返回的消息為空就表示已經(jīng)調(diào)用MessageQueue.quit();并且已經(jīng)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對象,這里進行消息的分發(fā)
           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);
    }
    
  5. 基于以上知識,可實現(xiàn)主線程給子線程(非主線程)發(fā)送消息。把下面例子中的mHandler聲明成類成員,在主線程通過mHandler發(fā)送消息即可。

    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線程中調(diào)用顯示,不需要Looper.perpare().因為它的Hander調(diào)用的主線程Looper
                Looper.loop();  
                //在調(diào)用looper.quit()之前是不會被調(diào)用的
            }
        }
    
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容