了解一些没有坏处 - Handler 消息机制


我会通过讲解 Handler/Looper/MessageQueue/Message 这几个类的作用以及它们之间的协作,来简单的描述 Android 的消息机制。

1. Message

我们说的消息机制中的消息就是 Message 这个类

1.1 消息的组成

消息的标识

  • int what: Message 对象的唯一标识

消息携带的一些值

  • int arg1: 用来存储一些 integer 值,是 Bundle 的低成本替代方案
  • int arg2: 用来存储一些 integer 值,是 Bundle 的低成本替代方案
  • Object obj: 一个任意类型的对象
  • Bundle data: Bundle 数据

如何处理消息

  • Runnable callback: 一段可执行代码块,如果消息对象携带 callback 属性,则优先执行 callback 代码块
  • Handler target: 用来处理消息,可以通过重写 Handler 对象的 handleMessage 来实现具体的处理逻辑

除此之外,Handler 中也提供了另外一种对消息对象的处理,之后我们会说到。

其他属性

  • long when: 消息期望被处理的时间
  • Message next: 指向下一个消息对象,用来支持链表
  • int flags: 标志位,用来用来标识消息是否为异步,是否在使用中
static final int FLAG_IN_USE = 1 << 0;

static final int FLAG_ASYNCHRONOUS = 1 << 1;

可以看到,最低位为 1 表示消息在使用中,倒数第二位为 1 表示当前消息是异步消息

1.2 如何创建一个消息对象

Message 提供了一个无参的构造函数来创建 Message 对象

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

不过官方更推荐使用 obtain() 方法来创建 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() 放来来创建一个 Message 对象,然后在给对应的属性赋值,还有一些多参的重载方法我没有列出,它们的步骤和上述两个方法做的事情大致相同。

消息池
接着看这个 obtain() 方法是如何创建消息对象的:

/**
 * 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 内部用链表实现了一个全局池子,每次调用 obtain() 方法会从池子中捞取一个消息对象(链表头),如果消息对象为空,则通过构造函数来创建一个消息对象并返回。

  • static Message sPool: 消息列表的头节点
  • 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. 检查消息标记位,不在使用状态时,可以被回收
  2. 将标记为设置为使用中状态,同时将其他属性设置为初始化值
  3. 回收池不满的话,则放入回收池(头插法)

1.3 一些问题

看完上述对于 Message 的介绍之后,你可能能够回答这些问题?

  1. 创建一个 Message 对象的推荐做法
  2. Message 回收池的数据结构

也可能会有这些疑问?

  1. 异步消息有什么用?

在之后我们将介绍异步消息的作用。

2. 一个小例子

我们带着 Looper 和 Handler 创建有没有依赖关系? 这个问题来看这个例子

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

运行上面的代码, 程序将直接崩溃,错误日志如下:


子线程创建 Handler 报错

在一个没有调用 Looper.prepare() 方法的线程中无法创建 Handler,也就是说 Handler 必须在 Looper 线程中创建和使用。

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. 先调用 Looper.prepare() 方法创建一个 Looper 对象
  2. 创建 Handler 对象,并且重写 handleMessage 方法来处理输入的消息
  3. 调用 Looper.loop() 方法开启消息循环,该方法内会不断的取出消息然后进行分发处理

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() 方法会调用 prepare(true) 方法,表示不允许退出消息队列,false 则表示允许退出。

之后会调用 Looper 的构造函数 new Looper(quitAllowed) 来创建一个 Looper 对象,并且存储在 ThreadLocal 中(在此之前,我们先会判断当前线程有没有创建过 Looper 对象,一个线程只允许创建一个 Looper 对象)

有关于 TreadLocal 的介绍请看这里 Android的消息机制之ThreadLocal的工作原理

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

Looper 的构造函数比较简单

  1. 创建一个 MessageQueue 对象并且赋值给 mQueue 变量
  2. 获取当前线程对象并赋值给 mThread 变量

3.2 Looper.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.");
    }
    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 中有没有设置过 Looper 对象,没有的话则会抛出异常,在调用 loop() 方法之前必须调用 prepare() 方法。

然后开启一个循环:

  1. 通过 queue.next() 方法从 MessageQueue 中取出消息
  2. 通过设置的 Printer 打印 Dispatching 日志
  3. 调用 msg.target.dispatchMessage(msg) 进行消息处理
  4. 通过设置的 Printer 打印 Finished 日志
  5. 回收消息

3.3 检测耗时的消息

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

在 loop 方法的循环中,在处理消息前后都会通过 Printer 打印日志,我们可以自己实现 Printer 并且设置给 Looper,来统计每个消息的处理耗时(参考 BlockCanary 的实现)

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 实现 println 方法,来统计 dispatchMessage 消耗的时间,如果超过我们设置订的时间(3 秒钟)则会触发我们的回调函数
  2. 获取当前线程的 Looper 对象,并将自定义的 Printer 对象设置给 Looper 对象
  3. 发送一个消息,让线程休眠 5 秒钟来模拟耗时操作
  4. 执行上述代码,你将会看到 Blocking 的 Toast 提示

3.4 小结

打个比方,prepare 方法相当于创建了一个行李传送履带,loop 方法相当于启动行李传送履带,行李相当于我们发送的 Message,looper 会将最先放置在履带上的行李传送到终点进行处理。

4. Handler

对于 Handler 来说,我们需要关注的三个点是:

  1. 如何创建 Handler 对象
  2. 如何发送消息
  3. 如何处理消息

4.1 创建 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;
}

我们最常使用的无参构造函数,默认回调为空,并且发送的消息全部为同步消息,可以看到 Handler 的创建是依赖于 Looper 对象的。

同时我们也可以使用指定的 Looper 对象来创建 Handler:

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

4.2 使用 Handler 发送消息

首先看看我们最常用的 sendMessage(Message msg) 方法的调用链:

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

最终我们会调用 MessageQueue 的 enqueueMessage(Message msg, long when) 方法来完成消息的入队,可以想象是在传送带上放一个行李,实际上是一个单链表的插入操作。

  1. 将 this(Handler 对象)赋值给 msg.target 属性
  2. 根据创建 Handler 时传入的 mAsynchronous 标志,来决定消息是否异步
  3. 根据当前系统时间插入到链表中

4.3 处理消息

在介绍 Looper 的时候,在 loop 方法的 for 循环中获取到消息后,会调用 msg.target.dispatchMessage(msg) 方法进行消息处理,根据上面的描述,可以知道 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 不为空,则执行 callback 的 run() 方法,这类消息通常使用 post(Runnable r) 方法创建,会将 Runnable 对象赋值给 Message 的 callback 属性
  2. 如果 Handler 的 mCallback 对象不为空,则执行 Callback 对象的 handleMessage 方法,Callback 对象通过 Handler 的构造函数传入
  3. 实现一个 Handler 子类,并重写 handleMessage(Message msg) 方法来对消息进行处理

5. MessageQueue

先回顾一下文章的前半部分说了什么:

  • Message 的数据结构
  • Message 的复用机制
  • Looper 中通过 MessageQueue 的 next() 方法来获取消息
  • Looper 中通过 Message 的 target 属性来处理消息
  • Looper 中在处理消息前后打印日志,统计消息处理的耗时
  • Handler 中通过 MessageQueue 的 enqueueMessage(Message msg, long when) 方法根据 when 指定的时间,将 Message 插入链表合适的位置

最开始介绍 Message 的时候,我们已经知道消息内部有个 Message 类型的属性 next,用来支持链表。

Message 的回收池使用的链表这种数据结构,MessageQueue 当中同样是使用链表来存储消息:

Message mMessages

MessageQueue 内部的 mMessages 属性指向列表的头节点。

接下来重点放到消息的存放和取出上

5.1 enqueueMessage(Message msg, long when)

根据 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. 如果当前队列已经标记为退出,则将消息回收,入队失败
  3. 满足以下三种情况的话,会将消息插入链表头部
    1. 当前链表为空(p == null)
    2. 当前消息需要立刻执行(when == 0)
    3. 当前消息早于根节点消息的时间(when < p.when)
  4. 链表的比较操作,根据 when 值将链表插入合适的位置
  5. 如果 needWake == true,则唤醒消息队列
  6. 消息入队成功

消息的入队其实就是单链表的插入操作,不难理解,整个插入过程中会对 needWake 这个值进行修改,来决定是否唤醒消息队列(消息队列在没有消息的时候处于阻塞状态)

  • 插入链表头部
    • 消息队列阻塞,needWake = true
    • 消息队列不阻塞,needWake =false
  • 插入列表中
    • 头节点消息 target == null,并且当且入队消息是第一个异步消息,needWake = true

之前我们说过通过 enqueueMessage 方法插入的消息都会检查 target 属性是否为空,如果为空则抛出异常,那么这种 target == null 的消息是怎么插入链表中的呢?

5.2 postSyncBarrier()

MessageQueue 内部为我们提供了一个方法 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;
    }
}

可以看到在方法内部,创建了一个 Message 对象,并且根据时间插入到了链表合适的位置上,这个消息的插入不会唤醒消息队列。

这个消息我们称为是同步屏障,它的作用是拦截所有屏障之后的同步消息,异步消息则不受影响(我认为在这种情况下,异步消息的优先级提高了

同时记得需要调用 removeSyncBarrier(int token) 方法来删除同步屏障,不然我们的同步消息就无法执行了

5.3 next()

最后看看,如何从消息队列中取出消息,我删除了部分代码,保留了一些关键信息

Message next() {
    int pendingIdleHandlerCount = -1; // IdleHandler 数量
    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); 方法是一个阻塞操作(管道机制),当队列中没有消息的时候,或者没有消息现在需要处理的时候,代码会阻塞,这意味着 nativePollOnce 方法之后的代码不会执行(我理解阻塞是当前线程不再占用 CPU 资源)。
  2. nextPollTimeoutMillis 默认值为 0 ,意味着 nativePollOnce 方法不会阻塞,后续代码得到执行,等于 -1 的话意味着 nativePollOnce 方法会阻塞,等待着管道的写入信号,写入这一操作其实就是在介绍 enqueueMessage 时讲到的 nativeWake(mPtr) 方法,
  3. 然后开始从链表中取出消息对象
    1. 首先判断当前消息是不是一个同步消息屏障,如果遇到了同步消息屏障,则循环去找链表中第一个异步消息
    2. 找到异步消息后,根据时间判断当前是否需要执行异步消息,满足时间条件话,返回消息对象
    3. 如果异步消息不满足时间条件,则会将时间差值赋值给 nextPollTimeoutMillis 变量,在下次循环时,nativePollOnce 方法将会阻塞对应的时长
    4. 如果是同步消息,依然是对时间进行判断,需要执行的话,则返回消息对象,不然则会在下次循环的时候阻塞对应的时间
  4. 如果当前消息队列中没有消息,或者第一个消息对象还没到执行时间,我们会执行 IdleHandler 相关的处理,IdleHandler 的作用就像是它的名字一样,会在消息队列空闲时执行一些操作
    1. 如果当前消息队列没有设置 IdleHandler 则进行下次循环,并且设置 mBlock = true
    2. 如果当前消息队列的 IdleHandler 列表中有值,则会遍历 IdleHandler 列表,执行 IdleHandler 的 queueIdle() 方法
    3. 将 pendingIdleHandlerCount 置为 0,这意味 IdleHandler 在 next() 方法中只会执行一次
    4. 将 nextPollTimeoutMillis 设置为 0,下次循环调用 nativePollOnce 方法的时候不会阻塞,代码会继续执行去消息队列中取消息,这是因为在 IdleHandler 执行的过程中可能会有新的消息传递进来。

5.4 异步消息的用法

在我们刷新 UI 的时候会调用 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();在消息队列中设置了一个同步消息屏障,接着在 Choreographer 类中通过发送异步消息来完成一些绘制操作

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

通过这种方式来保证绘制的优先级,在绘制完毕后会删除同步消息屏障

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 操作,并且是在消息队列空闲的时候执行。

6. 小结

阅读完文章后你应该能够了解

  1. Handler 如何发送消息
  2. Looper 如何取消息
  3. MessageQueue 的消息出队、入队操作
  4. 同步消息屏障和异步消息的作用
  5. IdleHandler 有什么用
  6. Message 的结构和复用
  7. 如何统计消息处理的耗时
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,050评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,538评论 1 306
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,673评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,622评论 0 218
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,047评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,974评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,129评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,893评论 0 209
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,654评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,828评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,297评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,619评论 3 262
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,326评论 3 243
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,176评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,975评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,118评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,909评论 2 278

推荐阅读更多精彩内容