Handler的前世今生2——Looper

Looper文檔


Handler,MessageQueue,Looper這四大金剛,

  1. Message : 消息載體 ;
  2. Handler : 發送和處理消息;
  3. MessageQueue :存儲消息;
  4. Looper:傳輸消息(MessageQueue —> Handler) ;

1. Looper的功能

Handler究其本質就是用來實現線程間通信。在Android開發過程中,我們通常在子線程(Thread) 中將消息發送給 主線程(MainThread), 而 主線程(MainThread) 需要去獲取到消息,進而才能去處理消息。

誰來保證主線程(MainThread)獲取到消息呢?
Looper,捨我其誰…

源碼中對於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 {@link #prepare} in the thread that is to run the loop,and then {@link #loop} to have it process messages until the loop is stopped.
該類用於爲一個線程執行消息輪詢。默認情況下線程是沒有用於消息輪詢的looper的,需要在消息輪詢的線程中調用prepare()方法來創建一個Looper對象,然後調用loop()去處理消息直到輪詢停止。

因爲處理消息的線程是隻負責處理消息的,壓根不知道消息什麼時候來。所以才需要Looper不停的刺探軍情,爲線程去輪詢消息


2.Looper的使用前提

Handler的前世今生1——ThreadLocal 談到TheadLocal來保證Thread和Looper之間忠貞不渝的愛情。

 private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }
 private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

一個線程只有一個Looper
一個Looper只有一個MessageQueue.

Thread : Looper : MessageQueue = 1 : 1 : 1

顯而易見,Looper 從其對應的MessageQueue 中去輪詢消息

2.1 MainLooper

在開發過程中,有時候我們會通過getMainLooper() 來切換到UI線程上。而且我們在主線程中使用Handler時,並沒有調用prepare() 和 loop() 等方法。這中間發生了什麼有趣的事情呢?

這就需要我們去查看ActivityThread的main()方法。

 public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

        // Install selective syscall interception
        AndroidOs.install();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");
		//1. 調用了Looper的prepare()方法
        Looper.prepareMainLooper();

        // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
        // It will be in the format "seq=114"
        long startSeq = 0;
        if (args != null) {
            for (int i = args.length - 1; i >= 0; --i) {
                if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                    startSeq = Long.parseLong(
                            args[i].substring(PROC_START_SEQ_IDENT.length()));
                }
            }
        }
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        // 開啓消息循環
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

可以看出來,在Looper的使用方法上沒有什麼區別。

  1. prepareMainLooper()方法
public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

  1. getMainLooper()方法
 public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }

在Looper裏面,我們看到了synchronized + static的實際應用啦。

我們應該理解,

  1. 這裏保持同步,因爲它是主線程,肯定不能在多線程情況下出現問題;
  2. prepare(false),false 是因爲主線程不允許退出,除非應用退出。

3. Looper的loop()

上面講了那麼多,但是都沒有切入到Looper的功能。loop()方法纔是整個Looper類的核心。調用loop()才表示真正的發車啦。

實際上,我們都說Looper是負責消息輪詢的,若沒有消息則阻塞,該阻塞的邏輯實現是在MessageQueue的next()方法。以後會展開討論…

public static void loop() {
		// 1. 取到looper對象
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        // 2. 獲取MessageQueue
        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();
		// 3. 無限輪詢
        for (;;) {
        	// 這裏會阻塞,一定要記住,阻塞邏輯是在MessageQueue中實現的。而不是這裏
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
          
            // Make sure the observer won't change while processing a transaction.
            // Observer就是一個監聽事件發送過程的接口
            final Observer observer = sObserver;

            Object token = null;
            if (observer != null) {
                token = observer.messageDispatchStarting();
            }
            long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
            try {
            	// 重點在這裏,此處的target其實就是Handler
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
            }
           
            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            // 消息複用機制
            msg.recycleUnchecked();
        }
    }

3.1. loop()的流程

  1. 獲取Looper對象,然後拿到MessageQueue;
  2. 開啓輪詢,
    2.1. 通過MessageQueuenext() 獲取消息;
    2.2. 表面上,若獲取的消息爲空,則退出循環(可能退出循環嗎?不可能的);
    2.3. 通過Message.target(即Handler),調用 dispatchMessage(msg) 進行消息分發和處理。
    2.4 調用MessagerecycleUnchecked() ,進行消息回收。

再次強調:loop()方法沒有消息則阻塞,阻塞的邏輯是在MessageQueue的next()實現的。


4. Looper還有退出功能

Looper在沒有消息的時候進行阻塞,終歸還是要消耗一部分資源的。所以除了主線程,其他線程還是需要退出循環功能的。

PS:值得我們注意的是,退出功能最終也是交由MessageQueue實現的。

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

推薦使用quitSafely()

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

至於兩者的區別,跟線程池的退出機制類似…


5. 關於loop()方法的考點

5.1 loop()死循環爲什麼沒有阻塞主線程?

我們會看到ActivityThread的Looper.loop()方法是在main()方法的最後。而Android本身就是靠事件驅動的。所以Looper.loop()不斷地接收並處理消息。我們的代碼都是在這個循環中執行的,所以當然不會阻塞啦。具體可以自行百度。

5.2 loop()沒有消息時是如何處理的?

其實還是想強調一下:loop()的阻塞邏輯是在MessageQueue中實現的。


6. 總結

Looper只是用來負責消息輪詢的,與負責發送和接收處理消息的Handler是沒有任何關聯的。這一點也可以通過源碼來證實。涉及到的對應關係如下:

Looper : Thread : MessageQueue = 1 : 1 : 1

Looper : Handler = 1 : N

Looper 和 Handler是沒有任何聯繫的。
Looper 本身的阻塞功能退出功能都是MessageQueue實現的。


歡迎大家繼續收看 Handler的前世今生3——Message

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章