Looper.loop爲什麼不會阻塞掉UI線程?來,我們從源碼裏面找到答案

之前在學習Hanlder源碼的時候,剛好涉及到 Looper.loop 方面的知識,這裏進行一下回答

首先,在ActivityThread.main 方法中,可以找到Looper相關的初始化代碼,在這段代碼裏面做了兩件事, 1、初始化當前線程的Looper
2、開啓循環

  public static void main(String[] args) {
  //省略掉部分不相關代碼
     //..........
      //prepareMainLooper 方法在當前線程初始化了一個消息隊列不允許退出Looper
        Looper.prepareMainLooper();
        //..........
        Looper.loop();

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

進入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.");
        }
        //1、獲取到消息隊列
        final MessageQueue queue = me.mQueue;

        //省略部分不相關的代碼
        //..................
        
        //開啓死循環
        for (;;) {
            //2、拿到隊列中的消息
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
 
        //省略部分不相關的代碼
        //..................
            try {
                //3、執行隊列中的消息
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
       //省略部分不相關的代碼
        //..................
            msg.recycleUnchecked();
        }

loop()方法中,代碼非常簡單,去除掉一些無用的日誌打印和不相關的代碼,剩餘的就非常簡單了,分三步走
1、獲取到looper中的 MessageQueue
2、開啓一個死循環,從MessageQueue 中不斷的取出消息
3、執行取出來的消息 msg.target.dispatchMessage(msg);(順便說一下,Handler的handleMessage()方法就是在這一步執行的,有興趣的可以自己看看,這裏就不細說了)

在第二步裏面,會發生阻塞,如果消息隊列裏面沒有消息了,會無限制的阻塞下去,主線程休眠,釋放CPU資源,直到有消息進入消息隊列,喚醒線程。從這裏就可以看出來,loop死循環本身大部分時間都處於休眠狀態,並不會佔用太多的資源,真正會造成線程阻塞的反而是在第三步裏的 msg.target.dispatchMessage(msg)方法,因此如果在生命週期或者handler的Handler的handleMessage執行耗時操作的話,纔會真正的阻塞UI線程;

到這裏,已經從java層解釋了Looper.loop爲什麼不會阻塞掉UI線程;最後,再看一下queue.next()方法,畢竟代碼留個尾巴不看實在太憋屈了

 Message next() {
 
         // mPtr保存了NativeMessageQueue的指針,調用nativePollOnce進行等待
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            //nativePollOnce 兩個參數,nextPollTimeoutMillis 表示的是等待時間,-1的時候表示無限制等待
            //在這裏可以看出,如果消息隊列裏面沒有消息,就會一直等待,直到隊列裏面加入新的消息,喚醒線程
            nativePollOnce(ptr, nextPollTimeoutMillis);

            //線程被喚醒後,開始從隊列中取出消息
            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                
                if (msg != null) {
                    if (now < msg.when) {
                        //如果下一條消息執行時間還未到,則計算出剩餘需要阻塞的時間,給nativePollOnce方法,讓他阻塞指定的時間後,繼續執行
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 在這裏,直接取出方法
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // 隊列裏面沒有消息了,再次無限期阻塞
                    nextPollTimeoutMillis = -1;
                }
                //省略部分不相關的代碼
                //..................
                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }
                //省略部分不相關的代碼
                 //..................
                
        }
    }

從上面代碼可以看出來,在調用next方法的時候,如果有當前消息可以被立刻執行,就會直接返回,如果消息需要延遲執行,則會接着阻塞一段時間,到消息可以被執行的時候再繼續線程執行消息,如果隊列裏面沒消息了,就會無限期的阻塞下去,直到新的消息進入隊列喚醒線程;

至於什麼時候喚醒阻塞,就需要看看enqueueMessage(Message msg, long when) 方法了,在這個方法裏面,會將新的消息放到消息隊列裏面,並且判斷如果此時線程處於阻塞狀態,就會調用nativeWake()方法喚醒線程,繼續執行next()方法,取出隊列中的消息

最後總結一下:

loop()開啓死循環後,會命令MessageQueue通過 next()方法 取出之前儲存的消息,如果有立刻被拿出來執行msg.target.dispatchMessage(msg);如果此時MessageQueue中已經沒有消息了(大部分時候都沒有),MessageQueue就會無限期的阻塞下去nativePollOnce(ptr, nextPollTimeoutMillis),釋放cpu資源,這時候並不會造成UI線程卡頓,直到有新的消息存入隊列enqueueMessage(Message msg, long when),喚醒之前阻塞的線程 nativeWake(mPtr),繼續執行next()方法;

我只是從java層對問題進行了解答,時間倉促,可能有不完善的地方,對於有錯誤的地方,歡迎指正,共同學習進步

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