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

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

首先,在ActivityThread.main 方法中,能夠找到Looper相關的初始化代碼,在這段代碼裏面作了兩件事, 一、初始化當前線程的Looper
二、開啓循環bash

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

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
 
複製代碼

進入loop方法oop

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;

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

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

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

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

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方法的時候,若是有當前消息能夠被馬上執行,就會直接返回,若是消息須要延遲執行,則會接着阻塞一段時間,到消息能夠被執行的時候再繼續線程執行消息,若是隊列裏面沒消息了,就會無限期的阻塞下去,直到新的消息進入隊列喚醒線程;spa

至於何時喚醒阻塞,就須要看看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層對問題進行了解答,時間倉促,可能有不完善的地方,對於有錯誤的地方,歡迎指正,共同窗習進步

相關文章
相關標籤/搜索