安卓面试:如何提高Message的优先级?

Posted HXL~

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了安卓面试:如何提高Message的优先级?相关的知识,希望对你有一定的参考价值。

面试官:如何提高Message的优先级?

前言

在日常的开发中有的场景需要我们自己的Message尽快被执行,这时候就需要提高Message的处理优先级。想解决这个问题,就需要对MessageQueue的构造有一定的了解,MessageQueue通过一个链表的结构,根据Message的参数when的大小进行排序,将系统以及我们自己发出的所有Message串起来,when的值越小那么此Message在链表中的位置越靠前,当Looper需要处理一个Message的时候,就会从链表中找出一个符合时间要求的Message并分发处理。基于这个构造很容易想到的一种方法就是将Message插在列表的头部,那么这个Message肯定会被最优先处理。还有另外方法思路也可以间接的提高Message优先级:同步消息屏障和异步消息。

一、将Message插在列表的头部

MessageQueue通过一个链表的结构,根据Message的参数when的大小进行排序并连接起来,when越小那么在链表中的位置就越靠前,那首先弄明白的就是when代表的是什么?首先来看看我们用一个Handler发送一个Message的时候,when是什么

//Handler.java

    public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }

    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

    public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        return enqueueMessage(queue, msg, uptimeMillis);
    }

    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

Handler#sendMessage方法其实是调用了sendMessageDelayed方法,并且 delayMillis 传为 0. 当 delayMillis 小于 0 的时候,会默认赋值为0,所以当我们调用sendMessageDelayed方法时,如果传入的delayMillis为负数,其实最终会变成0. 然后 SystemClock.uptimeMillis() + delayMillis 作为参数uptimeMillis继续向下传递,最终调用queue.enqueueMessage(msg, uptimeMillis)。其实Message中的when就是 SystemClock.uptimeMillis() + delayMillis。 SystemClock.uptimeMillis()返回的是手机开机后的累积时间(单位是毫秒),delayMillis是希望延迟处理的时间。
所以我们正常的使用Handler发送Message时,when的数值一定是一个大于0的数。

回到我们的问题,想要Message的优先级变高,那就要想办法把Message插入到链表的头部,Handler还提供了以下的方法:

//Handler.java

    public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) {
        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, 0);
    }

这个方法很明显了,最后调用enqueueMessage的时候,直接将uptimeMillis置为0,这样message就排到链表的头部了。不过此时又有问题了,如果我在插入的时候,头部已经有一个when=0的Message了,这个时候怎么办?其实MessageQueue已经想到了,只要我插入的Message的when为0,那么不管此时链表是什么,都直接插入到头部。

//MessageQueue.java

boolean enqueueMessage(Message msg, long when) {
       
        synchronized (this) {
	    ...          

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
	        //只要when=0,直接插入到链表的头部
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            }

	    ...
    }

二、同步消息屏障和异步消息

另外一种思路就是可不可以在遍历链表的时候,跳过前边那些优先级不是那么高的Message,而定位到需要优先处理的Message,这样是不是也到达了提高Message优先级的效果?答案是肯定的。同步消息屏障和异步消息就是为了这个而存在的。

同步消息:我们平时使用Handler发送的Message大都是同步消息,此处的 “同步” 和多线程同步不是一个概念,只是对消息的一种区分。同步Message的isAsync参数为false。

异步消息:和同步消息唯一的区别就是isAsync参数为true。

同步消息屏障:首先他也是一个Message消息对象,但是他的target参数为null(这是屏障消息和普通唯一的区别)。屏障的意思就是屏蔽掉,所以同步消息屏障顾名思义就是屏蔽掉同步消息,帮助我们获取异步消息。

解释完这三种概念,我们来看一张图,直观的讲一下同步消息屏障和异步消息是如何配合工作的。

如图,三个消息组成一个MessageQueue,三个消息按照when的的数值顺序排列。在正常情况下当我们遍历链表的时候,由于同步消息A排在异步消息B的前边,所以同步消息A的优先级肯定是要高于异步消息B的。此时,同步消息屏障就发挥它的作用了。
当我们遍历这个链表时,首先发现第一个消息是一个同步消息屏障,此时我们跳过这个屏障沿着链表继续向下遍历,然后找到同步消息A,由于前边已经发现有同步消息屏障了,所以同步消息A被屏蔽了,我们继续向下遍历。最终找到异步消息B,并将异步消息B从链表总分离出来,进行后续的消息分发处理。这就是他的整个过程了!!!我们没有将消息B移动到链表的头部,但是消息B的优先级却提高了(比消息A先执行)。

下边来看看具体的实现代码吧:

#MessageQueue.java

    @UnsupportedAppUsage
    public int postSyncBarrier() {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }

    private int postSyncBarrier(long when) {
        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;
        }
    }

发送同步消息屏障,需要使用MessageQueue#postSyncBarrier方法,但是这个是对我们隐藏的,我们是不能直接调用的(想调用的话只能通过反射),看代码可以知道屏障消息就是一个普通的Message对象,只不过这个Message的target没有初始化所以为null。并且这个Message对象的when就是SystemClock.uptimeMillis(),也就是说屏障消息在插到消息队列的时候也需要遵循按照when的大小排序。

最后看一下MessageQueue#next方法,看一下从消息队列中取消息时是如何处理屏障消息和异步消息的:

//MessageQueue.java

    Message next() {

        ...

        Message prevMsg = null;
        Message msg = mMessages;
	//如果msg的target==null,则认为这个msg是同步消息屏障
        if (msg != null && msg.target == null) {
            // 由于msg是同步消息屏障,所以继续向后遍历,寻找异步消息
            do {
                prevMsg = msg;
                msg = msg.next;
            } while (msg != null && !msg.isAsynchronous());
        }

	...
	
	return msg;
    }

总结

这就是两种提高Message优先级的思路了。但是第二种同步消息屏障的方式,源码我们是不能直接调用的,说明系统不想让我们用。那既然不想让我们用这个api又为什么存在呢?其实同步消息屏障api被应用在屏幕刷新机制中了。因为屏幕刷新是最重要的消息,如果他的消息如果不能及时处理就会出现屏幕卡顿。所以安卓系统通过这个机制来提高刷新屏幕消息的优先级。那他为什么不开放给我们开发者使用呢?交给你自己思考了哈哈哈哈。

以上是关于安卓面试:如何提高Message的优先级?的主要内容,如果未能解决你的问题,请参考以下文章

安卓面试:如何提高Message的优先级?

安卓面试:如何提高Message的优先级?

Android Studio工程里如何去除SDK中安卓原生jar

如何在另一个片段中使用 YouTubePlayerFragment 加载 YouTubePlayer? (安卓)

java 代码片段【安卓】

代码片段