• Android Handler 机制(四):屏障消息(同步屏障)


    一、Handler Message 种类

    Handler的Message种类分为3种:

    • 普通消息
    • 屏障消息
    • 异步消息

    其中普通消息又称为同步消息,屏障消息又称为同步屏障。

    我们通常使用的都是普通消息,而屏障消息就是在消息队列中插入一个屏障,在屏障之后的所有普通消息都会被挡着,不能被处理。不过异步消息却例外,屏障不会挡住异步消息,因此可以这样认为:屏障消息就是为了确保异步消息的优先级,设置了屏障后,只能处理其后的异步消息,同步消息会被挡住,除非撤销屏障。

    二、屏障消息如何插入消息队列

    同步屏障是通过MessageQueue的postSyncBarrier方法插入到消息队列的。

    MessageQueue#postSyncBarrier方法的源码如下:

    private int postSyncBarrier(long when) {
            synchronized (this) {
                final int token = mNextBarrierToken++;
                //1、屏障消息和普通消息的区别是屏障消息没有tartget。
                final Message msg = Message.obtain();
                msg.markInUse();
                msg.when = when;
                msg.arg1 = token;
    
                Message prev = null;
                Message p = mMessages;
                //2、根据时间顺序将屏障插入到消息链表中适当的位置
                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;
                }
                //3、返回一个序号,通过这个序号可以撤销屏障
                return token;
            }
    }

    postSyncBarrier方法就是用来插入一个屏障到消息队列的,可以看到它很简单,从这个方法我们可以知道如下:

    • 屏障消息和普通消息的区别在于屏障没有tartget,普通消息有target是因为它需要将消息分发给对应的target,而屏障不需要被分发,它就是用来挡住普通消息来保证异步消息优先处理的。
    • 屏障和普通消息一样可以根据时间来插入到消息队列中的适当位置,并且只会挡住它后面的同步消息的分发。
    • postSyncBarrier返回一个int类型的数值,通过这个数值可以撤销屏障。
    • postSyncBarrier方法是私有的,如果我们想调用它就得使用反射。
    • 插入普通消息会唤醒消息队列,但是插入屏障不会。

    三、屏障消息的工作原理

    通过postSyncBarrier方法屏障就被插入到消息队列中了,那么屏障是如何挡住普通消息只允许异步消息通过的呢?

    我们知道MessageQueue是通过next方法来获取消息的。

    Message next() {
        //1、如果有消息被插入到消息队列或者超时时间到,就被唤醒,否则阻塞在这。
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {//2、遇到屏障  msg.target == null
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());//3、遍历消息链表找到最近的一条异步消息
            }
            if (msg != null) {
                //4、如果找到异步消息
                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;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            } else {
                // 如果没有异步消息就一直休眠,等待被唤醒。
                nextPollTimeoutMillis = -1;
            }
            //...
        }
    }

    在注释2如果碰到屏障就遍历整个消息链表找到最近的一条异步消息,在遍历的过程中只有异步消息才会被处理执行到 if (msg != null){}中的代码。

    屏障消息就是通过这种方式就挡住了所有的普通消息。

  • 相关阅读:
    那些年搞不懂的多线程、同步异步及阻塞和非阻塞(一)---多线程简介
    java中IO流详细解释
    Java IO流学习总结
    MySQL数据库中索引的数据结构是什么?(B树和B+树的区别)
    使用Redis存储Nginx+Tomcat负载均衡集群的Session
    Redis 3.2.4集群搭建
    JDK1.8源码分析之HashMap
    java HashMap和LinkedHashMap区别
    Java中原子类的实现
    多线程-传统定时任务
  • 原文地址:https://www.cnblogs.com/renhui/p/12875589.html
Copyright © 2020-2023  润新知