面试官问我:同步屏障和异步消息的运行机制

1,456 阅读4分钟

记得看文章三部曲,点赞,评论,转发。 微信搜索【程序员小安】关注还在移动开发领域苟活的大龄程序员,“面试系列”文章将在公众号同步发布。

1.前言

通过昨天的技术交流,天才少年成功闯过一关,来到二面现场。

2.正文

哎呀,怎么面试官跟昨天的是同一个人,不会是她对我有什么想法吧,嘿嘿, 是心动啊,糟糕眼神躲不掉,对你莫名的心跳,竟然停不了对你的迷恋~~

在这里插入图片描述

小伙子,今天的面试官又是我,是不是很激动,那么我们面试开始吧。 接着昨天聊得,今天讲一下消息的同步屏障和异步消息吧。

哈哈,Handler消息可是我的强项,想当年,那个夜晚。。。噗,怎么又回忆了。

在这里插入图片描述

Handler的Message简单来说可以分成三类: 1)普通消息----同步消息,我们平时使用的基本都是普通消息。 2)屏障消息----同步屏幕,Message消息中的target字段的值为null,当在消息队列中插入了屏障消息后,在屏障消息之后的普通消息将不会被执行,优先执行异步消息。类似于公司开会,屏障消息就是保安,当他站在人群中的时候,开完会,只能领导(异步消息)先走,等领导走完后,保安(屏障消息)取消后,我们这些打工者(普通消息)按照顺序依次回工位。 3)异步消息----调用setAsynchronous(true)设置为异步消息。

再深入一点吧

什么意思?是我的理解的那个意思吗,我有点惊慌失措。

在这里插入图片描述

你想什么呢?讲一下同步屏障如何创建。

在这里插入图片描述

同步屏障的创建很简单,通过调用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();//可以看到此Message没有target
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            if (when != 0) {//根据when字段,插入到MessageQueue队列中
                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;
        }
    }

上面关键代码已经注释过了,postSyncBarrier方法其实就是创建一个么有target的Message对象,然后根据when字段,插入到消息队列中。

那异步消息呢?如何创建的,消息屏障又是如何保证优先执行异步消息?

异步消息创建更简单,调用Message的setAsynchronous方法即可:

    public void setAsynchronous(boolean async) {
        if (async) {
            flags |= FLAG_ASYNCHRONOUS;
        } else {
            flags &= ~FLAG_ASYNCHRONOUS;
        }
    }

至于消息屏障如何保证异步消息优先执行,这个肯定要看Looper对象的loop方法了,对于Handler机制不了解,可以看我前面一篇文章Android消息处理机制(Handler、Looper、MessageQueue与Message) ,这一篇文章就够了,looper方法会循环通过queue.next方法获取待处理的Message对象,那核心代码肯定在这个queue.next方法中,让我们来跟踪一下:

 Message next() {
        //省略一些代码
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {//1.当target==null,是不是很熟悉,说明这个Message是同步屏障
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());//2.判断是不是异步消息,如果不是,则指针指向下一个,直到找到最近的异步消息
                }
                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;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;//3.找到待执行的异步消息,并返回他
                    }
                } else {
                    nextPollTimeoutMillis = -1;
                }
               //省略一些代码
    }

核心代码都做了注释,通过注释1会先判断,当前队列中是否包含屏障消息,如果包含则执行到注释2处,通过一个do/while循环查找最近的异步消息,然后返回该异步消息,如果异步消息都被分发,队列中没有异步消息,则会一直阻塞,等待唤醒。

那屏障消息如何移除呢?

屏障消息的移除同样是在MessageQueue中实现的,方法为removeSyncBarrier

public void removeSyncBarrier(int token) {
        // Remove a sync barrier token from the queue.
        // If the queue is no longer stalled by a barrier then wake it.
        synchronized (this) {
            Message prev = null;
            Message p = mMessages;
            //1.找到token对应的消息屏障
            while (p != null && (p.target != null || p.arg1 != token)) {
                prev = p;
                p = p.next;
            }
            if (p == null) {
                throw new IllegalStateException("The specified message queue synchronization "
                        + " barrier token has not been posted or has already been removed.");
            }
            final boolean needWake;
            //2.从消息链表中删除该消息屏障
            if (prev != null) {
                prev.next = p.next;
                needWake = false;
            } else {
                mMessages = p.next;
                needWake = mMessages == null || mMessages.target != null;
            }
            p.recycleUnchecked();

            // If the loop is quitting then it is already awake.
            // We can assume mPtr != 0 when mQuitting is false.
            if (needWake && !mQuitting) {
                nativeWake(mPtr);
            }
        }
    }

核心代码都做了注释,通过注释1会找到token对应的消息屏障,然后把该消息屏障从MessageQueue中删除。

恩,你对Handler消息了解很深刻,今天有点晚了,view的创建流程,mChoreographer发送消息的流程,咱们后面再聊。


微信搜索【程序员小安】“面试系列(java&andriod)”文章将在公众号同步发布。