美文网首页Android开发经验谈
我也是醉了这都还不会,Android 消息屏障与异步消息?

我也是醉了这都还不会,Android 消息屏障与异步消息?

作者: 程序员面试秘籍 | 来源:发表于2020-11-11 17:08 被阅读0次

    ❝Android 消息机制中的 MessageQueue 可以存放三种类型的消息,普通消息、消息屏障和异步消息。其中消息屏障和异步消息搭配使用,可以达到屏蔽普通消息、优先处理异步消息的目的。❞

    「目录:」

    1.如何插入一个消息屏障?
    2.如何删除一个消息屏障?
    3.如何插入一个异步消息?
    4.消息屏障对插入消息有什么影响?
    5.消息屏障是如何优先处理异步消息的?
    6.Framework 中哪里使用了消息屏障?

    1. 如何插入一个消息屏障?

    见 MessageQueue 的 postSyncBarrier 方法:

    private int postSyncBarrier(long when) {
        synchronized (this) {
            final int token = mNextBarrierToken++;
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;
            //按时间排序插入到队列中...
            return token;
        }
    }
    

    1.消息屏障不需分发处理,没有 target Handler,后续也会根据有无 target 来判断是否为消息屏障
    2.消息屏障也是有时间戳的,并且只会对后面的消息起到屏障作用,不会影响前面的消息
    3.消息屏障插入后无需唤醒线程,因为消息屏障原本的目的就是打算屏蔽消息处理的
    4.插入后会返回一个 token,用于后续移除指定 token 的消息屏障
    5.方法为 private,外部调用需反射

    2. 如何删除一个消息屏障?

    见 MessageQueue 的 removeSyncBarrier 方法:

    public void removeSyncBarrier(int token) {
        synchronized (this) {
            Message prev = null;
            Message p = mMessages;
            while (p != null && (p.target != null || p.arg1 != token)) {
                prev = p;
                p = p.next;
            }
            //从队列中删除这个消息屏障...
            if (needWake && !mQuitting) {
                nativeWake(mPtr);
            }
        }
    }
    

    1.根据无 target 及 token 匹配找到对应的消息屏障
    2.删除屏障后可能需要唤醒线程,是否唤醒取决于当前是否是因为消息屏障而阻塞的

    3. 如何插入一个异步消息?

    Message 的 setAsynchronous 为开放 API,直接调用设置即可,比如在 ViewRootImpl 中对输入事件的处理:

    public void dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
        SomeArgs args = SomeArgs.obtain();
        args.arg1 = event;
        args.arg2 = receiver;
        Message msg = mHandler.obtainMessage(MSG_DISPATCH_INPUT_EVENT, args);
        msg.setAsynchronous(true); //异步消息
        mHandler.sendMessage(msg);
    }
    

    由于输入事件需要快速响应,优先级比较高,所以设置为异步消息,避免被消息屏障屏蔽掉

    4. 消息屏障对插入消息有什么影响?

    见 MessageQueue 的 enqueueMessage 方法:

    //省略部分代码...
    Message p = mMessages;
    boolean needWake;
    if (p == null || when == 0 || when < p.when) { //插入到队列头部
        msg.next = p;
        mMessages = msg;
        needWake = mBlocked;
    } else { //插入到队列中间
        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;
        prev.next = msg;
    }
    if (needWake) { //唤醒
        nativeWake(mPtr);
    }
    

    1.如果插入到队列头部,那么只要当前线程是休眠的,就要唤醒,不管有没有消息屏障,因为消息屏障不会影响在它之前的消息
    2.如果插入到队列中间且队列头消息为消息屏障,那还要判断插入的消息是不是最早的异步消息,如果是才唤醒线程。因为如果之前已经有异步消息,那说明已经对之前的异步消息做过唤醒或休眠指定时间的处理了,不用再此唤醒

    5. 消息屏障是如何优先处理异步消息的?

    见 MessageQueue 的 next 方法:

    Message next() {
        //省略部分代码...
        for (;;) {
            nativePollOnce(ptr, nextPollTimeoutMillis);
            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
            //省略部分代码...
    }
    

    1.如果当前消息不是消息屏障,那异步消息和普通消息无异,都会按照时间排序依次执行
    2.如果当前消息为消息屏障,就会去找队列中的异步消息,如果没有异步消息,就无限休眠;如果有,就根据这个异步消息的处理时间去分发处理或休眠

    6. Framework 中哪里使用了消息屏障?

    ViewRootImpl 中界面绘制时使用了消息屏障:

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            //插入一个消息屏障,屏蔽普通消息的处理
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        }
    }
    
    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            //移除消息屏障
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        }
    }
    

    与输入事件一样,界面绘制也是优先级高的消息,需要优先处理,所以这里插入消息屏障 block 其他普通消息,以达到优先处理界面绘制的目的。

    原文链接:https://mp.weixin.qq.com/s?__biz=MzIzOTkwMDY5Nw==&mid=2247486257&idx=1&sn=daa26597b3e1dbd378268cb992441342&chksm=e9224a47de55c3518b8d84484c9c2aede91e427cea8f36e52a929fcefc02f51e50d013f192b5&mpshare=1&scene=23&srcid=1110jp91YbDzOTzyAji0wdrB&sharer_sharetime=1605017359550&sharer_shareid=5215d443920b86d04cceaaa42defb091#rd

    相关文章

      网友评论

        本文标题:我也是醉了这都还不会,Android 消息屏障与异步消息?

        本文链接:https://www.haomeiwen.com/subject/pqdsbktx.html