了解一些沒有壞處 - Handler 消息機制


我會通過講解 Handler/Looper/MessageQueue/Message 這幾個類的作用以及它們之間的協(xié)作,來簡單的描述 Android 的消息機制。

1. Message

我們說的消息機制中的消息就是 Message 這個類

1.1 消息的組成

消息的標識

  • int what: Message 對象的唯一標識

消息攜帶的一些值

  • int arg1: 用來存儲一些 integer 值,是 Bundle 的低成本替代方案
  • int arg2: 用來存儲一些 integer 值,是 Bundle 的低成本替代方案
  • Object obj: 一個任意類型的對象
  • Bundle data: Bundle 數(shù)據(jù)

如何處理消息

  • Runnable callback: 一段可執(zhí)行代碼塊,如果消息對象攜帶 callback 屬性,則優(yōu)先執(zhí)行 callback 代碼塊
  • Handler target: 用來處理消息,可以通過重寫 Handler 對象的 handleMessage 來實現(xiàn)具體的處理邏輯

除此之外,Handler 中也提供了另外一種對消息對象的處理,之后我們會說到。

其他屬性

  • long when: 消息期望被處理的時間
  • Message next: 指向下一個消息對象,用來支持鏈表
  • int flags: 標志位,用來用來標識消息是否為異步,是否在使用中
static final int FLAG_IN_USE = 1 << 0;

static final int FLAG_ASYNCHRONOUS = 1 << 1;

可以看到,最低位為 1 表示消息在使用中,倒數(shù)第二位為 1 表示當前消息是異步消息

1.2 如何創(chuàng)建一個消息對象

Message 提供了一個無參的構(gòu)造函數(shù)來創(chuàng)建 Message 對象

/** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
*/
public Message() {
}

不過官方更推薦使用 obtain() 方法來創(chuàng)建 Message 對象,我們來看看幾個重載的 obtain 方法

public static Message obtain(Handler h) {
    Message m = obtain();
    m.target = h;

    return m;
}

public static Message obtain(Handler h, int what) {
    Message m = obtain();
    m.target = h;
    m.what = what;

    return m;
}

上面列出的兩個重載的方法,都是先通過無參的 obtain() 放來來創(chuàng)建一個 Message 對象,然后在給對應(yīng)的屬性賦值,還有一些多參的重載方法我沒有列出,它們的步驟和上述兩個方法做的事情大致相同。

消息池
接著看這個 obtain() 方法是如何創(chuàng)建消息對象的:

/**
 * Return a new Message instance from the global pool. Allows us to
 * avoid allocating new objects in many cases.
 */
public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

可以看到,Message 內(nèi)部用鏈表實現(xiàn)了一個全局池子,每次調(diào)用 obtain() 方法會從池子中撈取一個消息對象(鏈表頭),如果消息對象為空,則通過構(gòu)造函數(shù)來創(chuàng)建一個消息對象并返回。

  • static Message sPool: 消息列表的頭節(jié)點
  • static int sPoolSize = 0: 當前消息池的大小
  • private static final int MAX_POOL_SIZE = 50: 默認消息池的大小

消息回收
既然可以從消息池中取消息,那消息是如何放進池子中的(消息回收)?

public void recycle() {
    if (isInUse()) {
        if (gCheckRecycle) {
            throw new IllegalStateException("This message cannot be recycled because it " + "is still in use.");
            }
        return;
    }
    recycleUnchecked();
}

void recycleUnchecked() {
    // Mark the message as in use while it remains in the recycled object pool.
    // Clear out all other details.
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = -1;
    when = 0;
    target = null;
    callback = null;
    data = null;

    synchronized (sPoolSync) {
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}
  1. 檢查消息標記位,不在使用狀態(tài)時,可以被回收
  2. 將標記為設(shè)置為使用中狀態(tài),同時將其他屬性設(shè)置為初始化值
  3. 回收池不滿的話,則放入回收池(頭插法)

1.3 一些問題

看完上述對于 Message 的介紹之后,你可能能夠回答這些問題?

  1. 創(chuàng)建一個 Message 對象的推薦做法
  2. Message 回收池的數(shù)據(jù)結(jié)構(gòu)

也可能會有這些疑問?

  1. 異步消息有什么用?

在之后我們將介紹異步消息的作用。

2. 一個小例子

我們帶著 Looper 和 Handler 創(chuàng)建有沒有依賴關(guān)系? 這個問題來看這個例子

package com.xiezhen.handlerstudy;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity {

    private TextView tvHandler;
    private Button btnUpdate;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tvHandler = (TextView) findViewById(R.id.tv_handler);

        new Thread(new Runnable() {

            @Override
            public void run() {
                new Handler();
            }
        }).start();
    }
}

運行上面的代碼, 程序?qū)⒅苯颖罎ⅲe誤日志如下:


子線程創(chuàng)建 Handler 報錯

在一個沒有調(diào)用 Looper.prepare() 方法的線程中無法創(chuàng)建 Handler,也就是說 Handler 必須在 Looper 線程中創(chuàng)建和使用。

3. Looper

我們來看下源碼中的示例代碼:

class LooperThread extends Thread {
    public Handler mHandler;
  
    public void run() {
        Looper.prepare();
  
        mHandler = new Handler() {
            public void handleMessage(Message msg) {
                // process incoming messages here
            }
        };
  
        Looper.loop();
    }
}
  1. 先調(diào)用 Looper.prepare() 方法創(chuàng)建一個 Looper 對象
  2. 創(chuàng)建 Handler 對象,并且重寫 handleMessage 方法來處理輸入的消息
  3. 調(diào)用 Looper.loop() 方法開啟消息循環(huán),該方法內(nèi)會不斷的取出消息然后進行分發(fā)處理

3.1 Looper.prepare()

public static void prepare() {
    prepare(true);
}

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));
}

無參的 prepare() 方法會調(diào)用 prepare(true) 方法,表示不允許退出消息隊列,false 則表示允許退出。

之后會調(diào)用 Looper 的構(gòu)造函數(shù) new Looper(quitAllowed) 來創(chuàng)建一個 Looper 對象,并且存儲在 ThreadLocal 中(在此之前,我們先會判斷當前線程有沒有創(chuàng)建過 Looper 對象,一個線程只允許創(chuàng)建一個 Looper 對象)

有關(guān)于 TreadLocal 的介紹請看這里 Android的消息機制之ThreadLocal的工作原理

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

Looper 的構(gòu)造函數(shù)比較簡單

  1. 創(chuàng)建一個 MessageQueue 對象并且賦值給 mQueue 變量
  2. 獲取當前線程對象并賦值給 mThread 變量

3.2 Looper.loop()

我刪除了部分代碼,僅保留了一些檢查邏輯和消息分發(fā)邏輯

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;
        }
        // This must be in a local variable, in case a UI event sets the logger
        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what);
        }
        try {
            msg.target.dispatchMessage(msg);
        } finally {
            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }
        msg.recycleUnchecked();
    }
}

首先會檢查當前 ThreadLocal 中有沒有設(shè)置過 Looper 對象,沒有的話則會拋出異常,在調(diào)用 loop() 方法之前必須調(diào)用 prepare() 方法。

然后開啟一個循環(huán):

  1. 通過 queue.next() 方法從 MessageQueue 中取出消息
  2. 通過設(shè)置的 Printer 打印 Dispatching 日志
  3. 調(diào)用 msg.target.dispatchMessage(msg) 進行消息處理
  4. 通過設(shè)置的 Printer 打印 Finished 日志
  5. 回收消息

3.3 檢測耗時的消息

public void setMessageLogging(@Nullable Printer printer) {
    mLogging = printer;
}

在 loop 方法的循環(huán)中,在處理消息前后都會通過 Printer 打印日志,我們可以自己實現(xiàn) Printer 并且設(shè)置給 Looper,來統(tǒng)計每個消息的處理耗時(參考 BlockCanary 的實現(xiàn))

package com.example.realxz.handlertest

import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.support.v7.app.AppCompatActivity
import android.util.Printer
import android.widget.Toast

const val THRESHOLD_MILLIS = 3000

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val obj = object : IBlockListener {
            override fun doOnBlock() {
                Toast.makeText(this@MainActivity, "Blocking", Toast.LENGTH_SHORT).show()
            }
        }
        val printer = TestPrinter(obj)

        val mainLooper = Looper.getMainLooper()
        mainLooper.setMessageLogging(printer)

        val handler = Handler(mainLooper)
        handler.post {
            Thread.sleep(5000)
        }
    }

    class TestPrinter(private var listener: IBlockListener) :Printer {
        private var printStarted = false
        private var startTime: Long = 0


        override fun println(x: String?) {
            if (!printStarted) {
                startTime = System.currentTimeMillis()
                printStarted = true
            } else {
                val endTime = System.currentTimeMillis()
                printStarted = false
                if (isBlock(endTime)) {
                    listener.doOnBlock()
                }
            }
        }

        private fun isBlock(endTime: Long) = (endTime - startTime) > THRESHOLD_MILLIS
    }

    interface IBlockListener {
        fun doOnBlock()
    }
}

  1. 自定義 Printer 實現(xiàn) println 方法,來統(tǒng)計 dispatchMessage 消耗的時間,如果超過我們設(shè)置訂的時間(3 秒鐘)則會觸發(fā)我們的回調(diào)函數(shù)
  2. 獲取當前線程的 Looper 對象,并將自定義的 Printer 對象設(shè)置給 Looper 對象
  3. 發(fā)送一個消息,讓線程休眠 5 秒鐘來模擬耗時操作
  4. 執(zhí)行上述代碼,你將會看到 Blocking 的 Toast 提示

3.4 小結(jié)

打個比方,prepare 方法相當于創(chuàng)建了一個行李傳送履帶,loop 方法相當于啟動行李傳送履帶,行李相當于我們發(fā)送的 Message,looper 會將最先放置在履帶上的行李傳送到終點進行處理。

4. Handler

對于 Handler 來說,我們需要關(guān)注的三個點是:

  1. 如何創(chuàng)建 Handler 對象
  2. 如何發(fā)送消息
  3. 如何處理消息

4.1 創(chuàng)建 Handler 對象

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

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;
}

我們最常使用的無參構(gòu)造函數(shù),默認回調(diào)為空,并且發(fā)送的消息全部為同步消息,可以看到 Handler 的創(chuàng)建是依賴于 Looper 對象的。

同時我們也可以使用指定的 Looper 對象來創(chuàng)建 Handler:

public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

4.2 使用 Handler 發(fā)送消息

首先看看我們最常用的 sendMessage(Message msg) 方法的調(diào)用鏈:

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);
}

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);
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

最終我們會調(diào)用 MessageQueue 的 enqueueMessage(Message msg, long when) 方法來完成消息的入隊,可以想象是在傳送帶上放一個行李,實際上是一個單鏈表的插入操作。

  1. 將 this(Handler 對象)賦值給 msg.target 屬性
  2. 根據(jù)創(chuàng)建 Handler 時傳入的 mAsynchronous 標志,來決定消息是否異步
  3. 根據(jù)當前系統(tǒng)時間插入到鏈表中

4.3 處理消息

在介紹 Looper 的時候,在 loop 方法的 for 循環(huán)中獲取到消息后,會調(diào)用 msg.target.dispatchMessage(msg) 方法進行消息處理,根據(jù)上面的描述,可以知道 msg.target 是一個 Handler 對象,我們看下 Handler 的 dispatchMessage(Message msg) 方法:

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}
  1. 如果 msg.callback 不為空,則執(zhí)行 callback 的 run() 方法,這類消息通常使用 post(Runnable r) 方法創(chuàng)建,會將 Runnable 對象賦值給 Message 的 callback 屬性
  2. 如果 Handler 的 mCallback 對象不為空,則執(zhí)行 Callback 對象的 handleMessage 方法,Callback 對象通過 Handler 的構(gòu)造函數(shù)傳入
  3. 實現(xiàn)一個 Handler 子類,并重寫 handleMessage(Message msg) 方法來對消息進行處理

5. MessageQueue

先回顧一下文章的前半部分說了什么:

  • Message 的數(shù)據(jù)結(jié)構(gòu)
  • Message 的復(fù)用機制
  • Looper 中通過 MessageQueue 的 next() 方法來獲取消息
  • Looper 中通過 Message 的 target 屬性來處理消息
  • Looper 中在處理消息前后打印日志,統(tǒng)計消息處理的耗時
  • Handler 中通過 MessageQueue 的 enqueueMessage(Message msg, long when) 方法根據(jù) when 指定的時間,將 Message 插入鏈表合適的位置

最開始介紹 Message 的時候,我們已經(jīng)知道消息內(nèi)部有個 Message 類型的屬性 next,用來支持鏈表。

Message 的回收池使用的鏈表這種數(shù)據(jù)結(jié)構(gòu),MessageQueue 當中同樣是使用鏈表來存儲消息:

Message mMessages

MessageQueue 內(nèi)部的 mMessages 屬性指向列表的頭節(jié)點。

接下來重點放到消息的存放和取出上

5.1 enqueueMessage(Message msg, long when)

根據(jù) when 指定的時間,將 msg 插入到鏈表合適的位置上

boolean enqueueMessage(Message msg, long when) {
    // 注意點1
    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) {
        // 注意點2
        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;
        
        // 注意點3
        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 {
            // 注意點 4
            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;
        }
        // 注意點 5
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}
  1. 首先檢測消息的 target 和 flags 屬性,不允許將 target 為空或者被標記為正在使用的消息通過 enqueueMessage 方法入隊
  2. 如果當前隊列已經(jīng)標記為退出,則將消息回收,入隊失敗
  3. 滿足以下三種情況的話,會將消息插入鏈表頭部
    1. 當前鏈表為空(p == null)
    2. 當前消息需要立刻執(zhí)行(when == 0)
    3. 當前消息早于根節(jié)點消息的時間(when < p.when)
  4. 鏈表的比較操作,根據(jù) when 值將鏈表插入合適的位置
  5. 如果 needWake == true,則喚醒消息隊列
  6. 消息入隊成功

消息的入隊其實就是單鏈表的插入操作,不難理解,整個插入過程中會對 needWake 這個值進行修改,來決定是否喚醒消息隊列(消息隊列在沒有消息的時候處于阻塞狀態(tài))

  • 插入鏈表頭部
    • 消息隊列阻塞,needWake = true
    • 消息隊列不阻塞,needWake =false
  • 插入列表中
    • 頭節(jié)點消息 target == null,并且當且入隊消息是第一個異步消息,needWake = true

之前我們說過通過 enqueueMessage 方法插入的消息都會檢查 target 屬性是否為空,如果為空則拋出異常,那么這種 target == null 的消息是怎么插入鏈表中的呢?

5.2 postSyncBarrier()

MessageQueue 內(nèi)部為我們提供了一個方法 postSyncBarrier 方法

public int postSyncBarrier() {
    return postSyncBarrier(SystemClock.uptimeMillis());
}
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;
        msg.arg1 = token;
        Message prev = null;
        Message p = mMessages;
        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;
    }
}

可以看到在方法內(nèi)部,創(chuàng)建了一個 Message 對象,并且根據(jù)時間插入到了鏈表合適的位置上,這個消息的插入不會喚醒消息隊列。

這個消息我們稱為是同步屏障,它的作用是攔截所有屏障之后的同步消息,異步消息則不受影響(我認為在這種情況下,異步消息的優(yōu)先級提高了

同時記得需要調(diào)用 removeSyncBarrier(int token) 方法來刪除同步屏障,不然我們的同步消息就無法執(zhí)行了

5.3 next()

最后看看,如何從消息隊列中取出消息,我刪除了部分代碼,保留了一些關(guān)鍵信息

Message next() {
    int pendingIdleHandlerCount = -1; // IdleHandler 數(shù)量
    int nextPollTimeoutMillis = 0; // 下次讀取消息的時間
    for (;;) {
    //注意點 1
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    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;
                    msg.markInUse();
                    return msg;
                }
            } else {
                nextPollTimeoutMillis = -1;
            }
        
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                mBlocked = true;
                continue;
            }
            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler
            boolean keep = false;
            try {
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }
            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }
        pendingIdleHandlerCount = 0;
        nextPollTimeoutMillis = 0;
    }
}
  1. nativePollOnce(ptr, nextPollTimeoutMillis); 方法是一個阻塞操作(管道機制),當隊列中沒有消息的時候,或者沒有消息現(xiàn)在需要處理的時候,代碼會阻塞,這意味著 nativePollOnce 方法之后的代碼不會執(zhí)行(我理解阻塞是當前線程不再占用 CPU 資源)。
  2. nextPollTimeoutMillis 默認值為 0 ,意味著 nativePollOnce 方法不會阻塞,后續(xù)代碼得到執(zhí)行,等于 -1 的話意味著 nativePollOnce 方法會阻塞,等待著管道的寫入信號,寫入這一操作其實就是在介紹 enqueueMessage 時講到的 nativeWake(mPtr) 方法,
  3. 然后開始從鏈表中取出消息對象
    1. 首先判斷當前消息是不是一個同步消息屏障,如果遇到了同步消息屏障,則循環(huán)去找鏈表中第一個異步消息
    2. 找到異步消息后,根據(jù)時間判斷當前是否需要執(zhí)行異步消息,滿足時間條件話,返回消息對象
    3. 如果異步消息不滿足時間條件,則會將時間差值賦值給 nextPollTimeoutMillis 變量,在下次循環(huán)時,nativePollOnce 方法將會阻塞對應(yīng)的時長
    4. 如果是同步消息,依然是對時間進行判斷,需要執(zhí)行的話,則返回消息對象,不然則會在下次循環(huán)的時候阻塞對應(yīng)的時間
  4. 如果當前消息隊列中沒有消息,或者第一個消息對象還沒到執(zhí)行時間,我們會執(zhí)行 IdleHandler 相關(guān)的處理,IdleHandler 的作用就像是它的名字一樣,會在消息隊列空閑時執(zhí)行一些操作
    1. 如果當前消息隊列沒有設(shè)置 IdleHandler 則進行下次循環(huán),并且設(shè)置 mBlock = true
    2. 如果當前消息隊列的 IdleHandler 列表中有值,則會遍歷 IdleHandler 列表,執(zhí)行 IdleHandler 的 queueIdle() 方法
    3. 將 pendingIdleHandlerCount 置為 0,這意味 IdleHandler 在 next() 方法中只會執(zhí)行一次
    4. 將 nextPollTimeoutMillis 設(shè)置為 0,下次循環(huán)調(diào)用 nativePollOnce 方法的時候不會阻塞,代碼會繼續(xù)執(zhí)行去消息隊列中取消息,這是因為在 IdleHandler 執(zhí)行的過程中可能會有新的消息傳遞進來。

5.4 異步消息的用法

在我們刷新 UI 的時候會調(diào)用 ViewRootImpl 類中的 scheduleTraversals 方法

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

注意在該方法中通過 mHandler.getLooper().getQueue().postSyncBarrier();在消息隊列中設(shè)置了一個同步消息屏障,接著在 Choreographer 類中通過發(fā)送異步消息來完成一些繪制操作

private void postCallbackDelayedInternal(int callbackType,
        Object action, Object token, long delayMillis) {
    synchronized (mLock) {
        final long now = SystemClock.uptimeMillis();
        final long dueTime = now + delayMillis;
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
        if (dueTime <= now) {
            scheduleFrameLocked(now);
        } else {
            Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
            msg.arg1 = callbackType;
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, dueTime);
        }
    }
}

通過這種方式來保證繪制的優(yōu)先級,在繪制完畢后會刪除同步消息屏障

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }
        performTraversals();
        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}

5.5 IdleHandler 的實際運用

ActivityThread 的 Handler 對象 mH 中會對 what == 120 的消息做如下處理

public static final int GC_WHEN_IDLE            = 120;

case GC_WHEN_IDLE:
    scheduleGcIdler();
    break;
    
void scheduleGcIdler() {
    if (!mGcIdlerScheduled) {
        mGcIdlerScheduled = true;
        Looper.myQueue().addIdleHandler(mGcIdler);
    }
    mH.removeMessages(H.GC_WHEN_IDLE);
}

當收到 120 的消息后,會在消息隊列中加入一個名叫 mGcIdler 的 IdleHander 對象,從名字上看是做一些 GC 操作

final class GcIdler implements MessageQueue.IdleHandler {
    @Override
    public final boolean queueIdle() {
        doGcIfNeeded();
        return false;
    }
}

void doGcIfNeeded() {
    mGcIdlerScheduled = false;
    final long now = SystemClock.uptimeMillis();
    //Slog.i(TAG, "**** WE MIGHT WANT TO GC: then=" + Binder.getLastGcTime()
    //        + "m now=" + now);
    if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) {
        //Slog.i(TAG, "**** WE DO, WE DO WANT TO GC!");
        BinderInternal.forceGc("bg");
    }
}

從代碼上看,確實是做了一些 GC 操作,并且是在消息隊列空閑的時候執(zhí)行。

6. 小結(jié)

閱讀完文章后你應(yīng)該能夠了解

  1. Handler 如何發(fā)送消息
  2. Looper 如何取消息
  3. MessageQueue 的消息出隊、入隊操作
  4. 同步消息屏障和異步消息的作用
  5. IdleHandler 有什么用
  6. Message 的結(jié)構(gòu)和復(fù)用
  7. 如何統(tǒng)計消息處理的耗時
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容