Handler 與 IdleHandler 淺析

版權聲明:本文章原創於 RamboPan ,未經允許,

Handler 分析

雖然 Handler 分析的比較多,可還是想記錄下自己分析的思路。

基於 :JKD 1.8 SDK 26

我們設計一個程序時,往往會執行很多小塊的代碼,而很多小塊的代碼還經常被調用,
那麼根據程序設計的原理,最好抽出公共部分,增加代碼的複用性。

這也是 Handler 使用時既有 sendMessage() 也有 postRunnable() 的原因。

sendMessage() 常用來傳遞某些變量,或者類似一個觸發的消息。
postRunnable() 常用來直接執行某個方法。

那麼許多代碼塊都是非立即執行的,那麼需要一個好的容器來管理這些任務就是很重要的。

先從 Handler 常用方法使用說起。首先,我們 new 一個新的 Handler

代碼【1】

Handler handler = new Handler();

public Handler() {
    this(null, async);
}

public Handler(Callback callback, boolean async) {
    ……
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

從常見的無參構造函數進來,順着重載可以看到兩個參數的構造函數。

按着代碼的順序來看,上面省略部分是如果有內存泄露潛在風險,則判斷是否是靜態的,如果不是會有提示。

下面的 mLooper = Looper.myLooper()mQueue = mLooper.mQueue() 實際都是從 Looper 中取得的,那這個留着等分析 Looper 的時候再來分析。

最後兩行代碼就是把傳入的 Callback 類型與布爾值存下來。

Handler 還有一個三參的構造函數,只是 Looper 獲取的方式有所差異。

那我們肯定就要用 Handler 發送消息。那就是 sendMessage()post()

代碼【2】

//如果直接發送消息,則默認是無延時
public final boolean sendMessage(Message msg)
{
    return sendMessageDelayed(msg, 0);
}

//根據當前時間加上需要延時的時間,作爲此條消息執行的時間
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    //檢查時間是否不合理
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

//使用 Handler (Looper 內的消息隊列) 內部的消息隊列進行消息插入,
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}

//在入隊前,把該 Handler 存在 Message 對象當中。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    //此處判斷是否是異步 Handler ,默認是同步 Handler
    //如果是異步,則每條插入的消息都會在此設爲異步
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

//需要執行 runnable 時
public final boolean post(Runnable r)
{
   return  sendMessageDelayed(getPostMessage(r), 0);
}

//獲取一條 Message 並將 runnable 存在 Message 中
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

Message{
    ……
    Runnable callback;
    ……
}

按我們一般的習慣,新建了 Handler ,那麼肯定要複寫它的 handleMessage() 方法。

那怎麼知道複寫哪個方法,或者消息的處理流程。我們來 Handler 代碼中找找有沒有分發消息的方法。

代碼【3】

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

//先判斷 message 是否自己帶有 runnable ,如果帶有,則執行 
private static void handleCallback(Message message) {
    message.callback.run();
}

//判斷 Handler 內部是否有的 callback 接口
//在構造 handler 時可以使用兩參的構造自己創建一個
public interface Callback {
    public boolean handleMessage(Message msg);
}

//只要沒有提前被 return ,就會走此處代碼
public void handleMessage(Message msg) {

}

從這個方法名來看,是負責分發消息的。三個方法也分別說明了。

按照這麼說,那我們可以通過後兩種方式修改來處理消息。

代碼【4】

Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        //todo
    }
};


//如果用這種方式,要注意與 handleMessage 的調用。
Handler handler = new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {
        //todo
        return false;
    }
});

Handler 基本說完了,剩下來說說 Looper

第一次認識的時候是, Handler 需要傳遞一個 Looper 變量。

正如代碼【1】 中

mLooper = Looper.myLooper();
if (mLooper == null) {
    throw new RuntimeException(
        "Can't create handler inside thread that has not called Looper.prepare()");
}        
mQueue = mLooper.mQueue;


//Handler 中的 Looper 是通過這方法獲取的
//這又是從 ThreadLocal 中獲取的,那 ThreadLocal 中又是怎麼出現的,暫時好像沒有思路
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

那既然沒有思路,我們還是先從構造函數開始尋找線索。

代碼【5】

//新建一個消息隊列 並且標記是否可以退出
//記錄下當前的線程
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}


//當我們調用 prepare 時,默認是可以退出的消息隊列
public static void prepare() {
    prepare(true);
}


/**
 * 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()}
 */
 //主線程調用時,是不可退出的消息隊列
 //從註釋中可以看出,這事安卓系統調用的。
public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

prepareMainLooper() 來看,我們猜想下,會不會主線程對應的 Looper 就是 mainLooper

我們找到 Looper 中關於返回 Looper 的方法, myLooper() getMainLooper()

我們在 Activity onCreate() 代碼中加入這麼一小段,測試下,是否正確。

代碼【6】

……
Looper myLooper = Looper.myLooper();
Looper mainLooper = Looper.getMainLooper();
Log.w("TAG->MainActivity","onCreate: myLooper : "+myLooper+" ; mainLooper : "+mainLooper);
new Thread(new Runnable(){
    @Override
    public void run(){
        Looper newThreadLooper=Looper.myLooper();
        Log.w("TAG->MainActivity","onCreate: newThreadLooper : "+newThreadLooper);
    }
}).start();
……

輸出結果:
TAG->MainActivity: onCreate: myLooper : Looper (main, tid 1) {1ad55b7b} ; mainLooper : Looper (main, tid 1) {1ad55b7b}

TAG->MainActivity: onCreate: newThreadLooper : null

果然,主線程對應的 Looper 就是 mainLooper ,並且我們在一個新線程中使用 Looper.myLooper() 返回的是一個 null

也說明了新的線程並沒有調用什麼方法生成對應的 Looper 。從代碼【1】中,Handler 的兩參構造函數,那個拋出的異常,也說明了:如果該線程對應的 Loopernull ,無法創建 Handler

這樣我們如果要在新的線程中使用 Handler ,那麼可以考慮兩種方式:

比如新線程也使用 Looper.prepare() 或者 Looper.getMainLooper() 使用主線程的 Looper

那我們先寫寫這兩種 Handler 創建。

代碼【7】

new Thread(new Runnable() {
    @Override
    public void run() {
        Looper.prepare();
        Handler newLooperHandler = new Handler();
        Looper.loop();
        }
}).start();

new Thread(new Runnable() {
    @Override
    public void run() {
        Handler useMainLooperHandler = new Handler(Looper.getMainLooper());
    }
}).start();

運行起來,肯定是沒問題的。關於 Looper.loop() 方法,在 Looper.prepare() 註釋中說明了,如果需要把消息輪詢起來,那麼需要調用此方法。

如果沒有調用的話,使用該 Handler 無法處理消息,不信可以試試哈。

那我們來分析下 Looper.loop() 究竟幹嘛了。

代碼【8】

public static void loop() {
    ……
    //輪詢的是該 Looper 中的消息隊列。
    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);
        } finally {
            ……
        }
        ……
        msg.recycleUnchecked();
    }
}

簡化了下代碼,從 MessageQueue 中不斷取出消息,然後把取到的消息,使用 Message 中的 Handler(target) 對象,調用其分發消息方法,最後把該消息重置後循環。

我們可以觀察到調用 Looper.loop() 之後,一直會在 for 循環中尋找消息,如果沒有消息的話,就會終止。

從上面的註釋可以瞭解到 Message.next() 可能會阻塞,除非確定沒有消息的時候,表明消息隊列已經停止工作,Looper.loop() 將要停止輪詢消息了。

HandleLooper 的一些方法看出,涉及到消息的插入,取出都會有 MessageQueue 有關,有部分方法就是封裝的 MessageQueue 的方法,那麼接下來看看 MessageQueue

還是從構造函數開始吧。

代碼【9】

MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

構造函數很簡單,就是判斷是否這個隊列允許退出,在之前代碼【5】中,瞭解到普通消息隊列是傳入的 true ,而主線程消息隊列是傳入的 false

mPtr 暫時不知道是什麼,不過可以看到是 long 類型的,看來是存一個很大的數。

之前有幾個方法是通過 Handler 或者 Looper 來間接調用 MessageQueue 的方法,現在來看看。

代碼【10】

Handler.enqueueMessage() —> MessageQueue.enqueueMessage()

boolean enqueueMessage(Message msg, long when) {
    //判斷該消息是否有效
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    //確定要插入隊列時,先要把隊列鎖住,防止多個線程插入
    synchronized (this) {
        //如果正在退出,那麼消息插入失敗,並且把消息重置
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            Log.w(TAG, e.getMessage(), e);
            msg.recycle();
            return false;
        }
        
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // Inserted within the middle of the queue.  Usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

插入隊列的方式,就是一個單鏈表的插入,畫了個簡圖。有三種情況。

  • 隊列是空的,該消息爲首條消息。(左上)
  • 隊列是非空的,對比時間,該消息爲最前面一條消息。(右上)
  • 隊列是非空的,對比時間,該消息爲中間一條消息。(右下)

    插入消息比較簡單,需要分析的是關於阻塞與是否喚醒。

代碼【11】

    //如果當前隊列沒有消息,或者該消息需要馬上執行,或者該消息,小於最前端消息
    if (p == null || when == 0 || when < p.when) {
        msg.next = p;
        mMessages = msg;
        //此時如果阻塞,則需要喚醒
        needWake = mBlocked;
    } else {
        // Inserted within the middle of the queue.  Usually we don't have to wake
        // up the event queue unless there is a barrier at the head of the queue
        // and the message is the earliest asynchronous message in the queue.
        
        //此時會先判斷是否被 barrier 阻塞,該消息是否是異步消息
        needWake = mBlocked && p.target == null && msg.isAsynchronous();
        Message prev;
        for (;;) {
            prev = p;
            p = p.next;
            if (p == null || when < p.when) {
                break;
            }
            //如果需要喚醒,前面已經有一條異步消息則取消喚醒。
            if (needWake && p.isAsynchronous()) {
                needWake = false;
            }
        }
        msg.next = p; // invariant: p == prev.next
        prev.next = msg;
    }
    //最後在判斷是否需要喚醒
    // We can assume mPtr != 0 because mQuitting is false.
    if (needWake) {
        nativeWake(mPtr);
    }

從之前代碼【8】中 Looper.loop() 也可以看到取消息的時候是有可能阻塞的,因爲一旦返回空消息,loop() 方法就結束了。

所以在 MessageQueue 中阻塞也說明了這個問題,如果暫時沒有消息或者是需要執行的消息,就進行阻塞,等到需要喚醒的時候再喚醒。

說到這,需要說一個新的東西,就是 barrier ,也關於到爲什麼構造 Handler 時需要傳入是否爲異步參數。

搜索 barrier 可以看到這麼一個方法。

代碼【12】

private int postSyncBarrier(long when) {
    // Enqueue a new sync barrier token.
    // We don't need to wake the queue because the purpose of a barrier is to stall it.
    synchronized (this) {
        final int token = mNextBarrierToken++;
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        //累加一個值作爲 barrier 的標記放在 msg.arg1 中
        msg.arg1 = token;

        Message prev = null;
        Message p = mMessages;
        //根據 barrier 的時間對比消息隊列中的時間,找合適的位置進行插入。
        if (when != 0) {
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        //重新將該點前後節點的引用進行修改
        if (prev != null) { // invariant: p == prev.next
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        return token;
    }
}

該方法前面還有很長一段註釋,簡單的說就是。


有時我們按這個時間的執行順序,把一些任務排列好了,但是可能碰到一種情況,就是可能因爲某些條件不滿足,此時需要等待條件滿足後,再按照順序執行完。

但是也可能此時會遇到這個隊列中需要某些任務不受這個條件的約束,到了該事件,就需要執行這個任務。當然我們可以新建兩個隊列,一個受條件約束,一個不受,不過這樣會多一份的消耗。

於是想出了這麼一個方法。需要條件等待時,就插入一個 barrier,阻塞隊列,部分消息就一直等待,這部分消息我們稱爲同步消息;另一部分消息不受條件約束時,也就是不受 barrier 影響,稱爲異步消息。

這兩種消息都放在同一個隊列中,如果沒有 barrier 時,兩種消息都差不多,沒區別。
當有 barrier 時,尋找消息時,就暫時不考慮同步消息,查找是否有滿足執行條件的異步消息,沒有就等待。


明白了這麼一個大的前提,我們來看看這段代碼,類似於插入消息。在不同的位置進行阻塞,和前面插入消息類似,就不再分析了。

不過需要注意一點的是,barriertarget 對象,是沒有保存任何 Handler ,所以在消息隊列時,會通過判斷 message.target == null 來判斷該 message 是正常的消息,還是 barrier

因爲定了執行時間的消息,可能會因爲 barrier 導致到時間沒有執行,而每次取消息時,也會查找隊列中所有以前的消息,然後都執行完。纔會進入等待。

說到了插入消息,那麼肯定要說取消息了。

代碼【13】

Looper.loop() —> MessageQueue.next()

Message next() {
    ……
    for (;;) {
        ……
        synchronized (this) {
            //找到當前的時間
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            //獲取隊首指針指向消息
            Message msg = mMessages;
            //如果第一個消息是 barrier (上面分析出 target == null 時爲 barrier)
            if (msg != null && msg.target == null) {
                //一直向後尋找,直到碰到一個異步消息,跳出 while 循環
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            //如果是找同步消息,則直接判斷隊首消息,
            //判斷該該消息時間是否小於當前時間
            if (msg != null) {
                if (now < msg.when) {
                    // Next message is not ready.  Set a timeout to wake up when it is ready.
                    //如果當前時間小於消息執行時間,則繼續等待,
                    //等待時間爲 該消息繼續等待時間 與 int最大值 中大的一個。
                    //可以推測,如果該消息等待時間足夠長,那麼在 Integer.MAX_VALUE 時間後,
                    //肯定會喚醒一次,然後再繼續判斷
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    //獲取了一條可執行的消息,此時阻塞需要改爲 false
                    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 {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }

            //如果已經在退出的話,就返回空消息.
            //這個之前在 Looper.loop() 的註釋中也說明了,如果返回 null 證明隊列已經在退出了。
            //如果 Looper.loop() 不返回消息,就是阻塞在 MessageQueue 中。
            if (mQuitting) {
                dispose();
                return null;
            }
            ……
        }
        ……
    }
}

因爲中間有些其他邏輯的影響,我簡化了下,其他代碼一會再分析下,大概的分析都已經用註釋寫出了。

  • 我覺得很巧妙的是,第一個 if 語句對異步消息的查找,如果第一個不爲 barrier 的話,直接跳過異步消息查找,走下一段代碼,直接就從第一條消息開始判斷。

  • 第二處就是 Looper.loop() 是一直循環的,沒有消息的時候會阻塞,類似線程掛起,等到有消息時再喚醒,Message.next() 返回 null 表明消息隊列退出,而 Looper.loop() 也是因此而停止。

  • 第三處是推測,nextPollTimeoutMillis 在有消息的時候,是一個大的和時間有關的正值。而在沒有消息的時候。nextPollTimeoutMillis = -1 ,應該是一直等待的意思。(當然僅是推測)

代碼【14】

    Message next() {
        ……
        //初始設置爲 0
        int nextPollTimeoutMillis = 0;
        for (;;) {
            //當不爲 0 時進行一些操作
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            nativePollOnce(ptr, nextPollTimeoutMillis);
            ……
            synchronized (this) {
                ……
                if (msg != null) {
                    if (now < msg.when) {
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    }else{
                        ……
                    }
                }else {
                    nextPollTimeoutMillis = -1;
                }
            }
    }

這是簡化後關於 nextPollTimeoutMillis 有關的邏輯,初始設爲 0 ,然後有消息時,會修改 nextPollTimeoutMillis 的值。

如果沒有消息時,爲 -1。並且把這個類似時間的值作爲參數使用 nativePollOnce() 方法,彷彿是和底層一些相關的操作。

剩下的方法看起來像是和 IdeHandler 有關的。前幾次看這個 next() 方法被幹擾了,現在再看逐漸清晰。

當然,還是先看看 IdleHandler 的結構代碼。

代碼【15】

/**
 * Callback interface for discovering when a thread is going to block
 * waiting for more messages.
 */
public static interface IdleHandler {
    /**
     * Called when the message queue has run out of messages and will now
     * wait for more.  Return true to keep your idle handler active, false
     * to have it removed.  This may be called if there are still messages
     * pending in the queue, but they are all scheduled to be dispatched
     * after the current time.
     */
    boolean queueIdle();
}

雖然看着多,但就兩行代碼。從代碼中可以看出這是個簡單的接口,有一個帶返回值的實現。
從註釋中可以瞭解到,當線程阻塞空閒時會被調用。

queueIdle() 返回值: 如果還需要執行,則返回 true ;如果以後不再執行,則返回 false。

IdleHandler 通過 MessageQueue.addIdleHandler() 方法添加,類似 Handler.post(Runnable) 方式。

既然有點明白了 IdleHandler ,那麼就可以來看 next() 方法中其餘部分了。

代碼【16】

Message next() {
    ……
    //第一次默認爲 -1 ,需要去獲取外部 idleHandler 數量。
    int pendingIdleHandlerCount = -1; 
    for (;;) {
        synchronized (this) {
            //如果之前輪詢,沒有找到合適的消息
            //不會返回消息,纔有機會執行接下來的代碼。
            ……
            
            //第一次是因爲 pendingIdleHandlerCount = -1 ,取一次 idleHandler 數量
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            //這次判斷大小是確認是否有需要處理的 idlHandler
            //如果沒有的話,則將 mBlocked 改爲阻塞狀態
            if (pendingIdleHandlerCount <= 0) {
                mBlocked = true;
                continue;
            }
            
            //如果有需要執行的 idleHandler ,那麼就繼續下面這段代碼
            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            //把數組列表中的 idleHandler 複製一份到數組中
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        //挨着順序從數組中取出對應的 idleHandler 
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler

            boolean keep = false;
            //執行 idleHandler.queueIdle() 方法。
            try {
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }
            
            //根據 idler.queueIdle() 返回結果判斷該消息是否從數組列表中移除。
            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }

        //重置數量,保證每次 next() 時,只會執行一次 IdleHandler 方法。
        pendingIdleHandlerCount = 0;
    }
}

代碼中也簡單說明了邏輯,那我們肯定需要跑點代碼看看來驗證下。

onCreate() 中加入如下代碼。

代碼【17】

Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        Log.d("TAG->Main", "handleMessage" );
    }
};

handler.sendEmptyMessageDelayed(1, 2000);

//第一個試着返回 true.
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
    @Override
    public boolean queueIdle() {
        Log.d("TAG->MainActivity", "queueIdle true " );
        return true;
    }
});
//第一個試着返回 false.
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
    @Override
    public boolean queueIdle() {
        Log.d("TAG->MainActivity", "queueIdle false " );
        return false;
    }
});

打印結果:
TAG->MainActivity: queueIdle true

TAG->MainActivity: queueIdle false

TAG->MainActivity: queueIdle true

TAG->MainActivity: queueIdle true

TAG->MainActivity: queueIdle true

TAG->MainActivity: queueIdle true

TAG->MainActivity: queueIdle true

TAG->MainActivity: queueIdle true

果然返回 falseIdleHandler 只執行了一次,後面不再執行。不過 true 的 IdleHandler 怎麼執行這麼多?

忍不住摸下了屏幕,有點點發現,不管我觸摸屏幕,還是鎖屏。都會增加新的 log 。是不是也側面說明,這些也是使用的 MessageQueue

那我們現在可以來理下這個 MessageQueue().next() 的邏輯了,大致兩種情況。

  • 空隊列時,第一條消息插入,先判斷是否需要馬上執行,如果不需要馬上執行,走 IdleHandler 下方代碼。然後在此阻塞,等到有合適的消息時,返回該消息,此次 next() 方法執行完成。

  • 空隊列時,第一條消息插入,並且該消息需要馬上執行,那麼按代碼的邏輯,肯定是先返回該消息,此次 next() 結束。然後 Looper.Loop() 中發起下一次 MessageQueue.next() 方法,此時沒有合適的消息,然後再走 IdleHandler 代碼,並且阻塞在這,等到下次有消息時再結束 next() 方法。

至於關於阻塞的代碼,我猜測應該與 nativePollOnce() Binder.flushPendingCommands() 有關,在阻塞的時候,native 層進行暫停,然後在一定時間後或者得到另一個信號時,暫停解除,接着走 java 層代碼。

代碼【18】

Message next() {
    ……
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        nativePollOnce(ptr, nextPollTimeoutMillis);
    }    
    ……
}

但是現在水平有限,就暫時分析到這吧,如果有不足之處或有錯誤,歡迎指出,一起進步。

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