Handler异步消息和同步屏障SyncBarrier

2022-06-17 Views android

Handler的消息还有异步的?怎个异步法?UI线程的Handler执行时不是统一在UI线程吗,怎么能异步?如果你有这样的疑问,那你需要看看源码咯。

异步消息

Handler的异步消息不是按照执行时同步异步来说的,这个异步消息对于Message来说就是一个标记位,标记当前消息是一个特别的消息。

    /**
     *如果是true的话标明该消息是个异步消息,不受同步屏障的约束
     */
    public boolean isAsynchronous() {
        return (flags & FLAG_ASYNCHRONOUS) != 0;
    }


    /**
     * 设置为异步消息,不受同步消息屏障约束
     * <p>
     *对于某些操作,比如view的刷新,会引入同步屏障消息在Looper的消息队列中,防止后续的消息一直分发直到某些情况导致view的刷新被卡住。在调用View#invalidate后就会发送messages通过同步屏障挂起,直到View的下一帧即将要Draw。同步屏障确保UI刷新在能够正常执行。
     * </p><p>
     *异步消息对于同步屏障来说是免疫的。这些通常是指一些中断操作、输入事件或者是其他处理事件的信号,此时需要挂起其他工作。
     * </p><p>
     * </p>
     *标识异步消息顺序在分发时对于有序的同步消息来说是无用的。如果你的消息需要保持一定的顺序请谨慎使用
     * @param async True if the message is asynchronous.
     *
     * @see #isAsynchronous()
     */
    public void setAsynchronous(boolean async) {
        if (async) {
            flags |= FLAG_ASYNCHRONOUS;
        } else {
            flags &= ~FLAG_ASYNCHRONOUS;
        }
    }

通过setAsynchronous()和isAsynchronous()这2个方法注释,可以大概了解到异步消息不受同步消息屏障的约束,对于有序的同步消息来说,异步消息是无序的。

Handler同步屏障(SyncBarrier)

什么是Handler的同步屏障机制?通常我们使用Handler发消息的时候,都是用的默认的构造方法生成Handler,然后用send方法来发送消息,其实这时候我们发送的都是同步消息,发出去之后就会在消息队列里面排队处理。

我们都知道,Android系统16ms会刷新一次屏幕,如果主线程的消息过多,在16ms之内没有执行完,必然会造成卡顿或者掉帧。那怎么才能不排队,没有延时的处理呢?这个时候就需要异步消息,在处理异步消息的时候,我们就需要同步屏障,让异步消息不用排队等候处理。可以理解为同步屏障是一过滤网,把同步消息队列拦住,先处理异步消息,等异步消息处理完了,这网就会取消,然后继续处理同步消息。

所以上文说的异步消息不受同步屏障约束,是说同步屏障约束的是普通同步消息!

怎么来使用这个同步屏障呢?在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;
        }
    }

其实很简单,就是创建了一个消息,但是值得注意的是,这个消息没有target,普通的消息的必须有target的。然后我们来看看怎么取出消息。

处理异步消息

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

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                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());
                }
            省略代码
        }
    }

这里可以看到有一个很关键的判断,上面我们知道屏障消息的target为空,所以这里判断为true,while循环直到取出异步消息终止。接下来的处理就跟同步消息一样了,这里不赘述。

如何发送异步消息

  1. Message#setAsynchronous方法设置消息为异步消息
    public void setAsynchronous(boolean async) {
         if (async) {
             flags |= FLAG_ASYNCHRONOUS;
         } else {
             flags &= ~FLAG_ASYNCHRONOUS;
         }
     }
    
  2. 通过Handler的构造方法,创建一个只能发送异步消息的Handler
    public Handler(boolean async)
    public Handler(@Nullable Callback callback, boolean async)
    public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async)
    

如何应用

发送异步消息要搭配发送同步屏障一起使用才能生效。

发送同步屏障消息只能通过反射操作。

    public void postSyncBarrier() {
	   Method method = MessageQueue.class.getDeclaredMethod("postSyncBarrier");
	   token = (int) method.invoke(Looper.getMainLooper().getQueue());
   }

   public void removeSyncBarrier() {
	   Method method = MessageQueue.class
	.     getDeclaredMethod("removeSyncBarrier", int.class);
	    method.invoke(Looper.getMainLooper().getQueue(), token);}
   }

发送同步屏障消息后,异步消息处理完成,需要主动移除屏障,否则主线程会一直挂着!

在Android系统里面为了更快响应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();
    }
}

赏 支付宝扫码打赏 微信打赏

△