Finally figured out what is a synchronization barrier

background

Today, I suddenly heard the discussion of synchronization barriers next door. When I heard this name, I vaguely remembered that there Handlerwas a synchronization barrier mechanism, but why is the specific principle a bit vague? Just like a celebrity, you look familiar, but you just can’t remember his name, which makes obsessive-compulsive disorder patients like me extremely uncomfortable, so I took the time to pick up the synchronization barrier.

synchronization barrier mechanism

1. Go straight to the point, the word synchronization barrier mechanism sounds awesome, can you explain it in a simple way, first let everyone understand what it does?

Synchronization barrier is actually literal. It can be understood as establishing a barrier, isolating synchronous messages, and prioritizing asynchronous messages in the message queue for processing, so it is called a synchronization barrier.

2. The second question, what is the synchronization message? What is the difference between asynchronous messages and synchronous messages?

To answer this question, we have to understand Messagethat Messagethere are three types of messages:

  • Normal message (synchronous message)
  • asynchronous message
  • synchronization barrier message

The messages we usually use to Handlersend are basically ordinary messages, which are queued in the message queue in a regular manner, and then obediently come out to execute when it is its turn.

Consider a scenario, I now send a message to the UI thread, and want to draw a key View, but now the message queue of the UI thread is full of messages, and my message has not been processed for a long time, resulting in this The key View cannot be drawn, and the user is very annoyed when using it, and gives a bad review in a fit of anger. What kind of junk app is this?

At this point, synchronization barriers come in handy. If there is a synchronization barrier message in the message queue, it will first look for the message we want to process first, and take it out of the queue, which can be understood as urgent processing. How does the synchronization barrier mechanism know which message we want to process first? If a message is an asynchronous message, the synchronization barrier mechanism will give priority to it.

3. How to set up asynchronous messages? What kind of message is an asynchronous message?

MessageA ready-made flag bit has been provided isAsynchronousto mark whether this message is an asynchronous message.

4. Can you look at the source code to understand how the official implementation is done?

看看怎么往消息队列 MessageQueue 中插入同步屏障消息吧。

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) {
            // 根据when找到同步屏障消息插入的位置
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        // 插入同步屏障消息
        if (prev != null) {
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            // 前面没有消息的话,同步屏障消息变成队首了
            mMessages = msg;
        }
        return token;
    }
}

在代码关键位置我都做了注释,简单来说呢,其实就像是遍历一个链表,根据 when 来找到同步屏障消息应该插入的位置。

5.同步屏障消息好像只设置了when,没有target呢?

这个问题发现了华点,熟悉 Handler 的朋友都知道,插入消息到消息队列的时候,系统会判断当前的消息有没有 targettarget 的作用就是标记了这个消息最终要由哪个 Handler 进行处理,没有 target 会抛异常。

boolean enqueueMessage(Message msg, long when) {
  // target不能为空
  if (msg.target == null) {
      throw new IllegalArgumentException("Message must have a target.");
  }
  ...
}

问题 4 的源码分析中,同步屏障消息没有设置过 target,所以它肯定不是通过 enqueueMessage() 添加到消息队列里面的啦。很明显就是通过 postSyncBarrier() 方法,把一个没有 target 的消息插入到消息队列里面的。

6.上面我都明白了,下面该说说同步屏障到底是怎么优先处理异步消息的吧?

OK,插入了同步屏障消息之后,消息队列也还是正常出队的,显然在队列获取下一个消息的时候,可能对同步屏障消息有什么特殊的判断逻辑。看看 MessageQueuenext 方法:

Message next() {
    ...
            // msg.target == null,很明显是一个同步屏障消息
            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());
            }
    ...
}

方法代码很长,看源码最主要还是看关键逻辑,也没必要一行一行的啃源码。这个方法中相信你一眼就发现了 msg.target == null,前面刚说过同步屏障消息的 target 就是空的,很显然这里就是对同步屏障消息的特殊处理逻辑。用了一个 do...while 循环,消息如果不是异步的,就遍历下一个消息,直到找到异步消息,也就是 msg.isAsynchronous() == true

7.原来如此,那如果消息队列中没有异步消息咋办?

如果队列中没有异步消息,就会休眠等待被唤醒。所以 postSyncBarrier()removeSyncBarrier() 必须成对出现,否则会导致消息队列中的同步消息不会被执行,出现假死情况。

8.系统的 postSyncBarrier() 貌似也没提供给外部访问啊?这我们要怎么使用?

确实我们没办法直接访问 postSyncBarrier() 方法创建同步屏障消息。你可能会想到不让访问我就反射调用呗,也不是不可以。

但我们也可以另辟蹊径,虽然没办法创建同步屏障消息,但是我们可以创建异步消息啊!只要系统创建了同步屏障消息,不就能找到我们自己创建的异步消息啦。

系统提供了两个方法创建异步 Handler

public static Handler createAsync(@NonNull Looper looper) {
    if (looper == null) throw new NullPointerException("looper must not be null");
    // 这个true就是代表是异步的
    return new Handler(looper, null, true);
}

public static Handler createAsync(@NonNull Looper looper, @NonNull Callback callback) {
    if (looper == null) throw new NullPointerException("looper must not be null");
    if (callback == null) throw new NullPointerException("callback must not be null");
    return new Handler(looper, callback, true);
}

异步 Handler 发送的就是异步消息。

9.那系统什么时候会去添加同步屏障呢?

有对 View 的工作流程比较了解的朋友想必已经知道了,在 ViewRootImplrequestLayout 方法中,系统就会添加一个同步屏障。

不了解也没关系,这里我简单说一下。

(1)创建 DecorView

当我们启动了 Activity 后,系统最终会执行到 ActivityThreadhandleLaunchActivity 方法中:

final Activity a = performLaunchActivity(r, customIntent);

这里我们只截取了重要的一行代码,在 performLaunchActivity 中执行的就是 Activity 的创建逻辑,因此也会进行 DecorView 的创建,此时的 DecorView 只是进行了初始化,添加了布局文件,对用户来说,依然是不可见的。

(2)加载 DecorView 到 Window

onCreate 结束后,我们来看下 onResume 对应的 handleResumeActivity 方法:

@Override
public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
        boolean isForward, String reason) {
    ...
    // 1.performResumeActivity 回调用 Activity 的 onResume
    if (!performResumeActivity(r, finalStateRequest, reason)) {
        return;
    }
    ...
    final Activity a = r.activity;
    ...
    if (r.window == null && !a.mFinished && willBeVisible) {
        r.window = r.activity.getWindow();
        // 2.获取 decorview
        View decor = r.window.getDecorView();
        // 3.decor 现在还不可见
        decor.setVisibility(View.INVISIBLE);
        ViewManager wm = a.getWindowManager();
        WindowManager.LayoutParams l = r.window.getAttributes();
        a.mDecor = decor;
        l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
        l.softInputMode |= forwardBit;
        ...
        if (a.mVisibleFromClient) {
            if (!a.mWindowAdded) {
                a.mWindowAdded = true;
                // 4.decor 添加到 WindowManger中
                wm.addView(decor, l);
            } else {
                a.onWindowAttributesChanged(l);
            }
        }
    }
    ...
}

注释 4 处,DecorView 会通过 WindowManager 执行了 addView() 方法后加载到 Window 中,而该方法实际上是会最终调用到 WindowManagerGlobaladdView() 中。

(3)创建 ViewRootImpl 对象,调用 setView() 方法

// WindowManagerGlobal.ddView()
root = new ViewRootImpl(view.getContext(), display);
root.setView(view, wparams, panelParentView);

WindowManagerGlobaladdView() 会先创建一个 ViewRootImpl 实例,然后将 DecorView 作为参数传给 ViewRootImpl,通过 setView() 方法进行 View 的处理。setView() 的内部主要就是通过 requestLayout 方法来请求开始测量、布局和绘制流程

(4)requestLayout() 和 scheduleTraversals()

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        // 主要方法
        scheduleTraversals();
    }
}

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        // 1.将mTraversalScheduled标记为true,表示View的测量、布局和绘制过程已经被请求。
        mTraversalScheduled = true;
        // 2.往主线程发送一个同步屏障消息
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        // 3.注册回调,当监听到VSYNC信号到达时,执行该异步消息
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

看到了吧,注释 2 的代码熟悉的很,系统调用了 postSyncBarrier() 来创建同步屏障了。那注释 3 是啥意思呢?mChoreographer 是一个 Choreographer 对象。

要理解 Choreographer 的话,还要明白 VSYNC

我们的手机屏幕刷新频率是 1s 内屏幕刷新的次数,比如 60Hz、120Hz 等。60Hz表示屏幕在一秒内刷新 60 次,也就是每隔 16.6ms 刷新一次。屏幕会在每次刷新的时候发出一个 VSYNC 信号,通知CPU进行绘制计算,每收到 VSYNC,CPU 就开始处理各帧数据。这时 Choreographer 就上场啦,当有 VSYNC 信号到来时,会唤醒 Choreographer,触发指定的工作。它提供了一个回调功能,让业务知道 VSYNC 信号来了,可以进行下一帧的绘制了,也就是注释 3 使用的 postCallback 方法。

当监听到 VSYNC 信号后,会回调来执行 mTraversalRunnable 这个 Runnable 对象。

final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        // 移除同步屏障
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }
        // View的绘制入口方法
        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}

在这个 Runnable 里面,会移除同步屏障。然后调用 performTraversals 这个View 的工作流程的入口方法完成对 View 的绘制。

这回明白了吧,系统会在调用 requestLayout() 的时候创建同步屏障,等到下一个 VSYNC 信号到来时才会执行相应的绘制任务并移除同步屏障。所以在等待 VSYNC 信号到来的期间,就可以执行我们自己的异步消息了。

参考

requestLayout竟然涉及到这么多知识点

关于Handler同步屏障你可能不知道的问题

“终于懂了” 系列:Android屏幕刷新机制—VSync、Choreographer 全面理解

Guess you like

Origin juejin.im/post/7258850748150104120