Handler機制實現原理(三)Looper的源碼分析

剛看源碼的時候:「這TM寫的是啥?那寫的又TM是啥?」 研究明白了以後:「奧,原來就這點玩意兒啊,太簡單了。」緩存

Looper的職責很單一,就是單純的從MessageQueue中取出消息分發給消息對應的宿主Handler,所以它的代碼很少(300行左右)。安全

Looper是線程獨立的且每一個線程只能存在一個Looper。bash

Looper會根據本身的存活狀況來建立和退出屬於它本身的MessageQueue。ide

建立與退出Looper

上面的結論中提到了Looper是線程獨立的且每一個線程只能存在一個Looper。因此構造Looper實例的方法相似於單例模式。隱藏構造方法,對外提供了兩個指定的獲取實例方法prepare()prepareMainLooper()oop

// 應用主線程(UI線程)Looper實例
    private static Looper sMainLooper;

    // Worker線程Looper實例,用ThreadLocal保存的對象都是線程獨立的
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    // 與當前Looper對應的消息隊列
    final MessageQueue mQueue;

    // 當前Looper因此的線程
    final Thread mThread;

    /**
     * 對外公開初始化方法
     *
     * 在普通線程中初始化Looper調用此方法
     */
    public static void prepare() {
        // 初始化一個能夠退出的Looper
        prepare(true);
    }

    /**
     * 對外公開初始化方法
     *
     * 在應用主線程(UI線程)中初始化Looper調用此方法
     */
    public static void prepareMainLooper() {
        
        // 由於是主線程,初始化一個不容許退出的Looper
        prepare(false);

        synchronized (Looper.class) {
            // 若是sMainLooper不等於空說明已經建立過主線程Looper了,不該該重複建立
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

    /**
     * 內部私有初始化方法
     * @param quitAllowed 是否容許退出Looper
     */
    private static void prepare(boolean quitAllowed) {
        // 每一個線程只能有一個Looper
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        // 保存實例
        sThreadLocal.set(new Looper(quitAllowed));
    }

    /**
     * 私有構造方法
     * @param quitAllowed 是否容許退出Looper
     */  
    private Looper(boolean quitAllowed) {
        // 初始化MessageQueue
        mQueue = new MessageQueue(quitAllowed);
        // 獲得當前線程實例
        mThread = Thread.currentThread();
    }

複製代碼

真正建立Looper實例的構造方法中其實很簡單,就是建立了對應的MessageQueue實例,而後獲得當前線程,值得注意的是MessageQueue和線程實例都是被final關鍵字修飾的,只能被賦值一次。性能

對外公開初始化方法prepareMainLooper()是爲應用主線程(UI線程)準備的,應用剛被建立就會調用該方法,因此咱們不應再去調用它。ui

開發者能夠經過調用對外公開初始化方法prepare()對本身的worker線程建立Looper,可是要注意只能初始化一次。this

調用Looper.prepare()方法初始化完成後,能夠調用myLooper()myQueue()方法獲得當前線程對應的實例。spa

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

    public static @NonNull MessageQueue myQueue() {
        return myLooper().mQueue;
    }

複製代碼

退出Looper線程

退出Looper有安全與不安全兩種退出方法,其實對應的就是MessageQueue的安全與不安全方法:

public void quit() {
        mQueue.quit(false);
    }

    public void quitSafely() {
        mQueue.quit(true);
    }

複製代碼

什麼安全退出,什麼是不安全退出,在MessageQueue源碼中分析過。

運行Looper處理消息

調用Looper.prepare()方法初始化完成Looper後就可讓Looper去工做了,只須要調用Looper.loop()方法便可。

public static void loop() {
        // 獲得當前線程下的Looper
        final Looper me = myLooper();

        // 若是還沒初始化過拋異常
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }

         // 獲得當前線程下與Looper對應的消息隊列
        final MessageQueue queue = me.mQueue;

        // 獲得當前線程的惟一標識(uid+pid),做用是下面每次循環都判斷一下線程有沒有被切換
        // 不知道爲何要調用兩次該方法
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        // 進入死循環不斷取出消息
        for (;;) {

            // 從隊列中取出一個消息,這可能會阻塞線程
            Message msg = queue.next(); 

            // 若是消息是空的,說明隊列已經退出了,直接結束循環,結束方法
            if (msg == null) {
                return;
            }

            // 打印日誌
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            // 性能分析相關的東西
            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }

            try {
                //嘗試將消息分發給宿主(Handler)
                //dispatchMessage爲宿主Handler的接收消息方法
                msg.target.dispatchMessage(msg);
            } finally {
                 // 性能分析相關的東西
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

            //打印日誌
            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }


            //獲得當前線程的惟一標識
            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);
            }
      
            //消息分發完畢,回收消息到緩存池
            msg.recycleUnchecked();
        }
    }

複製代碼

總結

Looper的功能很簡單,核心方法Looper.loop()就是不斷的從消息隊列中取出消息分發給對應的宿主Handler,它與對應MessageQueue息息相關,一塊兒建立,一塊兒退出。

Looper更想強調的是線程的獨立性與惟一性,利用ThreadLocal保證每一個線程只有一個Looper實例的存在。利用靜態構造實例方法保證不能重複建立Looper。

Looper.prepareMainLooper()是比較特殊的方法,它是給UI線程準備,理論上開發者在任何狀況下都不該該調用它。

相關文章
相關標籤/搜索