ViewRootImpl#applyTransactionOnDraw 的坑

前情提要

之前在帮一同事做 SurfaceView 相关的内容(他本人对 SurfaceControl 相关接口不太熟悉)。在即将解决需要使用到帧同步接口的时候, 无意间看到了 SurfaceView 使用到了 ViewRootImpl#applyTransactionOnDraw 来做帧同步内容。

1
2
3
4
5
6
7
8
9
private void applyTransactionOnVriDraw(Transaction t) {
final ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null) {
// If we are using BLAST, merge the transaction with the viewroot buffer transaction.
viewRoot.applyTransactionOnDraw(t);
} else {
t.apply();
}
}

我一看好啊,以前老是写这种代码来做帧同步:

1
2
3
viewRoot.registerRtFrameCallback(frame -> {
viewRoot.mergeWithNextTransaction(t, frame);
});

虽然不怎么麻烦,但是贯彻简洁主义的我是这样想的:两次方法调用 < 一次方法调用。于是在简单看了下这接口的实现之后就决定以后就用这接口了。

遇到的问题

后面在做桌面动画需求的时候,鉴于我喜欢偷懒,于是也直接使用了这个接口来同步动画结束时的 finishTransaction。但在测试频繁打断动画的时候 却发现了动画结束的时候居然会闪一下,看着就像没帧同步的样子(信念崩塌)。

于是赶紧去仔细看了下这个接口的实现,好家伙,原来这接口只是保证了 Transaction 一定能生效,但可能是通过帧同步生效的,并不一定。 具体是怎么合并到 BLASTBufferQueue 的,可以自行看一下 ViewRootImpl#registerCallbacksForSync 这个方法,我们只需要看帧同步不生效的原因就行了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 具体在 ViewRootImpl#performTraversal 里
if (!cancelAndRedraw) {
mReportNextDraw = false;
mLastReportNextDrawReason = null;
mActiveSurfaceSyncGroup = null;
if (mHasPendingTransactions) {
// TODO: We shouldn't ever actually hit this, it means mPendingTransaction wasn't
// merged with a sync group or BLASTBufferQueue before making it to this point
// But better a one or two frame flicker than steady-state broken from dropping
// whatever is in this transaction
mPendingTransaction.apply();
mHasPendingTransactions = false;
}
mSyncBuffer = false;
if (isInWMSRequestedSync()) {
mWmsRequestSyncGroup.markSyncReady();
mWmsRequestSyncGroup = null;
mWmsRequestSyncGroupState = WMS_SYNC_NONE;
}
}

好嘛,安排得明明白白,注释都写好了:为了避免事务状态丢失,提前调用 apply,闪一两帧总好过状态不对。

结尾

能怎么办,老实用回之前一直在用的帧同步方法呗。不过以后用这些接口的时候,还是得好好看下注释,没有注释就看下具体实现,避免再出现这种踩坑的问题。

作者

Hhvvg

发布于

2025-05-27

更新于

2025-05-27

许可协议