假设有这样一个场景,一个是应用主窗口,一个是带有内容的SurfaceControl(简称SC),根据输入事件改变View控件以及SC图层的形态。
这个问题很简单,先监听事件,在分别设置各自的Geometry:
SurfaceControl.Transaction pendingTransaction = new SurfaceControl.Transaction();
SurfaceControl sc;
View overlays;
int scWidth = 1080;
int scHeight = 2400;
int left = 0;
int top = 0;
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_MOVE:
float rawX = event.getRawX();
float rawY = event.getRawY();
float sx = rawX / scWidth;
float sy = rawY / scHeight;
overlays.setTranslationX(left);
overlays.setTranslationY(top);
overlays.getLayoutParams().width = (int) rawX;
overlays.getLayoutParams().height = (int) rawY;
overlays.requestLayout();
pendingTransaction.setPosition(sc, left, top);
pendingTransaction.setMatrix(sc, sx, 0, 0, sy);
pendingTransaction.apply();
break;
default:
break;
}
return super.onTouchEvent(event);
}
以上逻辑可以做到每次事件到来时,View 控件与SC 图层能够根据输入坐标保持统一的形态变化,如果当这个View的作用是为了遮住SC图层,上面的逻辑能够做到吗?显然是不能的,因为View的绘制与SC图层属性的生效是不同步的。SC具有快速生效的能力,而View控件的绘制需要时间,SF也需要等待GPU绘制完成才会做进一步的合成,这样就会导致快速滑动时View控件遮不住SC的情况。
之前的版本确实是无法达到这样的效果,但是随着BBQ机制的的介入,这样的需求就可以实现:
SurfaceControl.Transaction pendingTransaction = new SurfaceControl.Transaction();
SurfaceControl sc;
View overlays;
int scWidth = 1080;
int scHeight = 2400;
int left = 0;
int top = 0;
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_MOVE:
float rawX = event.getRawX();
float rawY = event.getRawY();
float sx = rawX / scWidth;
float sy = rawY / scHeight;
overlays.setTranslationX(left);
overlays.setTranslationY(top);
overlays.getLayoutParams().width = (int) rawX;
overlays.getLayoutParams().height = (int) rawY;
overlays.requestLayout();
pendingTransaction.setPosition(sc, left, top);
pendingTransaction.setMatrix(sc, sx, 0, 0, sy);
ViewRootImpl viewRoot = overlays.getViewRootImpl();
viewRoot.applyTransactionOnDraw(pendingTransaction);
break;
default:
break;
}
return super.onTouchEvent(event);
}
通过 applyTransactionOnDraw 方法就能够实现这样的需求:
frameworks/base/java/android/view/ViewRootImpl.java
// 注册下一帧绘制完成的回调,该回调发生在RT线程,且在swap交换前执行
1360 public void registerRtFrameCallback(@NonNull FrameDrawingCallback callback) {
1361 if (mAttachInfo.mThreadedRenderer != null) {
1362 mAttachInfo.mThreadedRenderer.registerRtFrameCallback(frame -> {
1363 try {
1364 callback.onFrameDraw(frame);
1365 } catch (Exception e) {
1366 Log.e(TAG, "Exception while executing onFrameDraw", e);
1367 }
1368 });
1369 }
1370 }
-------------------------------------------------------------------------------
10396 //直接执行的是BBQ的 mergeWithNextTransaction 方法
10397 public void mergeWithNextTransaction(Transaction t, long frameNumber) {
10398 if (mBlastBufferQueue != null) {
10399 mBlastBufferQueue.mergeWithNextTransaction(t, frameNumber);
10400 } else {
10401 t.apply();
10402 }
10403 }
--------------------------------------------------------------------------------
10414 @Override
10415 public boolean applyTransactionOnDraw(@NonNull SurfaceControl.Transaction t) {
10416 registerRtFrameCallback(frame -> {
10417 mergeWithNextTransaction(t, frame);
10418 });
10419 return true;
10420 }
实现原理就是监听下一帧绘制时机,将参数传递的事务所包含的修改合入到BBQ事务中,保证在同一帧生效:
二、应用进程与系统进程数据同步
以应用进入分屏模式为例,用户长按最近任务卡片,点击分屏按钮触发进入分屏的操作。看下系统是如何将SystemUI-系统服务-应用之间的事务操作进行同步。
1、SystemUI 请求Task准备同步下一帧。
启动分屏后,SystemUI进程通过 WindowOrganizer#applySyncTransaction() 方法发起原子请求,
WindowContainerTransaction 就是集合所有窗口配置更新的原子事务:
WindowContainerTransaction
收集一组要以原子方式应用的配置更改,可与 WindowContainerTransactionCallback 一起使用,以接收包含同步操作结果的事务。
1.1 WOC 收到客户端请求后会在同步引擎中创建 SyncGroup。
frameworks/base/services/core/java/com/android/server/wm/WindowOrganizerController.java
@Override
public int applySyncTransaction(WindowContainerTransaction t,
IWindowContainerTransactionCallback callback) {
final long ident = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
int syncId = -1;
if (callback != null) {
syncId = startSyncWithOrganizer(callback);
}
applyTransaction(t, syncId, null /*transition*/);
if (syncId >= 0) {
setSyncReady(syncId);
}
return syncId;
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
创建 SyncGroup 添加到 mActiveSyncs 待同步集合:
frameworks/base/services/core/java/com/android/server/wm/BLASTSyncEngine.java
int startSyncSet(TransactionReadyListener listener) {
final int id = mNextSyncId++;
final SyncGroup s = new SyncGroup(listener, id);
mActiveSyncs.put(id, s);
return id;
}
1.2 Task容器加入到同步引擎待同步集合,同時合入窗口属性更改。
frameworks/base/services/core/java/com/android/server/wm/WindowOrganizerController.java
private void applyTransaction(@NonNull WindowContainerTransaction t, int syncId,
@Nullable Transition transition) {
...
ArraySet haveConfigChanges = new ArraySet<>();
Iterator> entries =
t.getChanges().entrySet().iterator();
while (entries.hasNext()) {
final Map.Entry entry = entries.next();
final WindowContainer wc = WindowContainer.fromBinder(entry.getKey());
if (syncId >= 0) {
//将Task与对应子窗口加入到同步集合中
addToSyncSet(syncId, wc);
}
}
entries = t.getChanges().entrySet().iterator();
while (entries.hasNext()) {
final Map.Entry entry = entries.next();
final WindowContainer wc = WindowContainer.fromBinder(entry.getKey());
final Task task = wc.asTask();
final Rect surfaceBounds = entry.getValue().getBoundsChangeSurfaceBounds();
final SurfaceControl.Transaction sft = new SurfaceControl.Transaction();
final SurfaceControl sc = task.getSurfaceControl();
task.updateSurfacePosition(sft);
if (surfaceBounds.isEmpty()) {
sft.setWindowCrop(sc, null);
} else {
sft.setWindowCrop(sc, surfaceBounds.width(), surfaceBounds.height());
}
//合入Task窗口属性更改
task.setMainWindowSizeChangeTransaction(sft);
}
...
}
重点看下addToSync方法,首先将要同步的 WindowContainer 添加到指定 SyncId 的 SyncGroup 中,同时Task 容器的 mSyncState 修改为 SYNC_STATE_READY状态,WindowState 子窗口的 mSyncState 修改为 SYNC_STATE_WAITING_FOR_DRAW状态。
frameworks/base/services/core/java/com/android/server/wm/BLASTSyncEngine.java
private void addToSync(WindowContainer wc) {
if (!mRootMembers.add(wc)) {
return;
}
wc.setSyncGroup(this);
wc.prepareSync();
mWm.mWindowPlacerLocked.requestTraversal();
}
将Task容器 mSyncState 状态修改为 SYNC_STATE_READY:
frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java
boolean prepareSync() {
if (mSyncState != SYNC_STATE_NONE) {
// Already part of sync
return false;
}
for (int i = getChildCount() - 1; i >= 0; --i) {
final WindowContainer child = getChildAt(i);
child.prepareSync();
}
mSyncState = SYNC_STATE_READY;
return true;
}
将对应子窗口 mSyncState 状态修改为 SYNC_STATE_WAITING_FOR_DRAW:
frameworks/base/services/core/java/com/android/server/wm/WindowState.java
boolean prepareSync() {
if (!super.prepareSync()) {
return false;
}
mSyncState = SYNC_STATE_WAITING_FOR_DRAW;
//给窗口加上强制重绘的标记,保证应用触发内容绘制
requestRedrawForSync();
mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this);
mWmService.mH.sendNewMessageDelayed(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this,
BLAST_TIMEOUT_DURATION);
return true;
}
mSyncState 状态针对父容器与子窗口的状态作用是不同的,因为容器不参与绘制,能做的就是让自己处于随时等待同步的状态,因为校验时会从父容器到子窗口逐个校验,是否同步完成还是取决于子窗口的状态。而子窗口同步状态设置成 SYNC_STATE_WAITING_FOR_DRAW ,表示需要先等待窗口内容绘制完成。当窗口绘制完成会将应用的绘制同步到自己的 mSyncTransaction,然后再将窗口的同步状态改为SYNC_STATE_READY。这样才会正在触发* BLASTSyncEngine *的同步操作。
在 prepareSync 方法的最后给自己加了强制重绘的标记,在下次窗口布局刷新时,加窗口加入到resize的流程中,触发应用的UI绘制。
1.3 将對應 SyncGroup 中对应的状态修改为 Ready 状态
更新同步狀態,同时申请WMS刷新布局進行更新。
private void setReady(boolean ready) {
mReady = ready;
if (!ready) return;
mWm.mWindowPlacerLocked.requestTraversal();
}
2、应用端触发同步性质的内容重绘。
应用端收到窗口尺寸变化的回调提醒,申请布局遍历,向WMS服务申请窗口布局。WMS服务处理完布局更新后,会去收集对应窗口配置更改的消费者,同时将 RELAYOUT_RES_BLAST_SYNC添加到返回应用端的结果标识中:
如果因其他功能业务针对应用窗口的 Geometry 修改操作需要与应用端 Buffer 的绘制在同一帧生效。可以通过WindowState#applyWithNextDraw() 方法将消费者添加到下一帧的同步中。
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
public int relayoutWindow(Session session, IWindow client, LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewVisibility, int flags,
long frameNumber, ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
SurfaceControl outSurfaceControl, InsetsState outInsetsState,
InsetsSourceControl[] outActiveControls, Point outSurfaceSize) {
...
if (mUseBLASTSync && win.useBLASTSync() && viewVisibility != View.GONE) {
//收集该窗口数据同步的消费者
win.prepareDrawHandlers();
//强制触发重绘,保证数据同步
win.markRedrawForSyncReported();
//加上该标识后,应用端会将下一帧的绘制操作启动同步,交由系统服务处理
result |= RELAYOUT_RES_BLAST_SYNC;
}
}
return result;
}
应用进程完成布局更新的请求,之后的流程来到了应用端,在绘制周期内主要做了以下几件事:
- 在执行绘制流程前针对 Frame Drawing、Frame Commit 以及 Frame Complete 等绘制状态注册了对应的监听;
- 执行具体绘制流程;
- 提交绘制完成状态,当下一帧内所有待绘制元素完成绘制操作,就通知WMS。
frameworks/base/core/java/android/view/ViewRootImpl.java
private void performDraw() {
...
//注册绘制完成后提交合成前的回调
addFrameCallbackIfNeeded();
addFrameCommitCallbackIfNeeded();
//注册绘制完成的回调
boolean usingAsyncReport = addFrameCompleteCallbackIfNeeded(mReportNextDraw);
try {
boolean canUseAsync = draw(fullRedrawNeeded);
} finally {
}
//提交绘制操作完成的提醒
if (mReportNextDraw) {
mReportNextDraw = false;
if (mSurfaceHolder != null && mSurface.isValid()) {
SurfaceCallbackHelper sch = new SurfaceCallbackHelper(this::postDrawFinished);
SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);
} else if (!usingAsyncReport) {
if (mAttachInfo.mThreadedRenderer != null) {
mAttachInfo.mThreadedRenderer.fence();
}
pendingDrawFinished();
}
}
}
2.1 注册 Frame Drawing 状态监听
触发时机是RT线程完成绘制提交GPU后,swap 缓冲区前。在这期间BBQ还未收到缓冲区可消费的提醒,此时 ViewRootImpl 收到回调通知让mRtBLASTSyncTransaction准备接管BBQ下一帧提交,同时暂停UI线程下一帧的操作以及接管后阻塞BBQ内部线程。
private void addFrameCallbackIfNeeded() {
final boolean nextDrawUseBlastSync = mNextDrawUseBlastSync;
final boolean reportNextDraw = mReportNextDraw;
// 下一帧绘制请求回调
HardwareRenderer.FrameDrawingCallback frameDrawingCallback = frame -> {
// 如果标志为需要进行同步,则用 mRtBLASTSyncTransaction 将BBQ事务接过来处理
if (nextDrawUseBlastSync) {
mBlastBufferQueue.setNextTransaction(mRtBLASTSyncTransaction);
//监听事务生效,唤醒UI线程继续处理
mBlastBufferQueue.setTransactionCompleteCallback(frame, frameNumber -> {
mHandler.postAtFrontOfQueue(this::clearBlastSync);
});
} else if (reportNextDraw) {
mBlastBufferQueue.flushShadowQueue();
}
};
//注册回调
registerRtFrameCallback(frameDrawingCallback);
}
2.2 注册 Frame Complete 状态监听
触发时机是 swap 缓冲区后,此时 mRtBLASTSyncTransaction 已包含了下一帧应用的所有数据更新。 ViewRootImpl 收到回调通知将 mRtBLASTSyncTransaction 事务所包含的所有操作合并到 mSurfaceChangedTransaction。
private boolean addFrameCompleteCallbackIfNeeded(boolean reportNextDraw) {
mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(() -> {
long frameNr = mBlastBufferQueue.getLastAcquiredFrameNum();
mHandler.postAtFrontOfQueue(() -> {
if (mNextDrawUseBlastSync) {
mSurfaceChangedTransaction.merge(mRtBLASTSyncTransaction);
}
if (reportNextDraw) {
pendingDrawFinished();
}
if (frameWasNotDrawn) {
clearBlastSync();
}
});
});
return true;
}
当所有元素执行完绘制流程,通知 WMS 服务应用下一帧内容绘制完成,同时传递等待提交的事务 mSurfaceChangedTransaction :
private void reportDrawFinished() {
try {
mDrawsNeededToReport = 0;
mWindowSession.finishDrawing(mWindow, mSurfaceChangedTransaction);
} catch (RemoteException e) {
mSurfaceChangedTransaction.apply();
} finally {
mSurfaceChangedTransaction.clear();
}
}
3、系统服务处理下一帧数据同步
3.1 收集当前窗口下一帧的所有需要提交的操作
WMS服务收到应用绘制完成的提醒,重点工作就是将系统服务中其他消费者的相关修改与应用的下一帧内容同步到 mSyncTransaction:
postDrawTransaction 包含应用已绘制的下一帧内容、系统服务中申请同步的消费者变更;
mSyncTransaction 是窗口容器基类 WindowContainer 中的一个变量,主要用来处理各类型窗口的属性变更。
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
void finishDrawingWindow(Session session, IWindow client,
@Nullable SurfaceControl.Transaction postDrawTransaction) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
WindowState win = windowForClientLocked(session, client, false);
// 与之对应的 WindowState 做数据同步工作
if (win != null && win.finishDrawing(postDrawTransaction)) {
//同步没有异常,为了使下一帧数据同步生效,需要强制申请遍历窗口
win.setDisplayLayoutNeeded();
mWindowPlacerLocked.requestTraversal();
}
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
frameworks/base/services/core/java/com/android/server/wm/WindowState.java
boolean finishDrawing(SurfaceControl.Transaction postDrawTransaction) {
...
//执行数据消费者的请求,将自己的对应图层的修改合入到 postDrawTransaction,跟着应用下一帧一起提交
executeDrawHandlers(postDrawTransaction);
if (!onSyncFinishedDrawing()) {
return mWinAnimator.finishDrawingLocked(postDrawTransaction);
}
if (postDrawTransaction != null) {
//合入所有事务操作至 mSyncTransaction
mSyncTransaction.merge(postDrawTransaction);
}
mWinAnimator.finishDrawingLocked(null);
// We always want to force a traversal after a finish draw for blast sync.
return true;
}
3.2 遍历待同步数据窗口,收集已准备好同步的操作
每次窗口遍历都会对所有有改动的Surface的进行设置事务操作,如果容器正在状态不是
SYNC_STATE_NONE,就会将 SystemUI 提交的原子操作 mMainWindowSizeChangeTransaction 合入到每个容器的 mSyncTransaction 事务中,紧接着检查 BLASTSyncEngine 是否存在待同步事务:
frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java
void performSurfacePlacementNoTrace() {
...
mWmService.openSurfaceTransaction();
try {
applySurfaceChangesTransaction();
} catch (RuntimeException e) {
} finally {
mWmService.closeSurfaceTransaction("performLayoutAndPlaceSurfaces");
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
mWmService.mSyncEngine.onSurfacePlacement();
...
}
这里的 mActiveSyncs 保存SystemUI 请求的待同步操作 SyncGroup,等待系统触发下一帧的同步校验,从而进一步收集各个窗口的同步数据。
frameworks/base/services/core/java/com/android/server/wm/BLASTSyncEngine.java
void onSurfacePlacement() {
// backwards since each state can remove itself if finished
for (int i = mActiveSyncs.size() - 1; i >= 0; --i) {
mActiveSyncs.valueAt(i).onSurfacePlacement();
}
}
SyncGroup 如果已准备就绪,会遍历原子提交中所有存在配置更改的容器,检查其是否已准备好同步数据,或者是否是不可见的,达到其中一个要求则通过校验。
frameworks/base/services/core/java/com/android/server/wm/BLASTSyncEngine.java
private void onSurfacePlacement() {
//SystemUI 申请原子提交后,WOC会将对应SyncGroup.mReady设置为ture
if (!mReady) return;
for (int i = mRootMembers.size() - 1; i >= 0; --i) {
final WindowContainer wc = mRootMembers.valueAt(i);
if (!wc.isSyncFinished()) {
return;
}
}
//收集所有同步事务的操作
finishNow();
}
3.3 将下一帧所有的同步操作交给SystemUI进行提交
SystemUI 此次原子操作所包含的对所有窗口配置更改的同步操作都已全部合入到 merged 事务,并通过远程回调将事务回传给SystemUI。
private void finishNow() {
SurfaceControl.Transaction merged = mWm.mTransactionFactory.get();
//逐个窗口进行同步
for (WindowContainer wc : mRootMembers) {
//将各个窗口的 mSyncTransaction 同步到的操作合入到 merged
wc.finishSync(merged, false /* cancel */);
}
//最终 merged 将包含所有同步的操作通过远程回调,传递到SystemUI 进程
mListener.onTransactionReady(mSyncId, merged);
mActiveSyncs.remove(mSyncId);
}
void finishSync(Transaction outMergedTransaction, boolean cancel) {
if (mSyncState == SYNC_STATE_NONE) return;
// 将 mSyncTransaction 同步到的操作合入到 outMergedTransaction
outMergedTransaction.merge(mSyncTransaction);
for (int i = mChildren.size() - 1; i >= 0; --i) {
mChildren.get(i).finishSync(outMergedTransaction, cancel);
}
mSyncState = SYNC_STATE_NONE;
if (cancel && mSyncGroup != null) mSyncGroup.onCancelSync(this);
mSyncGroup = null;
}
流程总结:
- SystemUI 向系统服务提交原子操作同步请求,操作中包含对 A B进程窗口的修改;
- 系统服务根据A B窗口的状态分别通知A B进程进行同步性质的内容重绘;
- A B进程内容绘制完毕,通过 BBQ 机制在内容绘制完毕后接管了BBQ的下一帧提交,并停止UI线程绘制,将各自接管的事务操作反馈给系统服务;
- 系统服务先合入其他消费者的事务,此时的事务已包含应用内绘制数据,消费者配置更改操作。再将该事务合入到对应窗口容器的用于同步的事务中(mSyncTransaction);
- 系统服务遍历窗口配置更改,将SystemUI 提交的原子操作也合入到对应窗口容器的用于同步的事务中;
- 系统服务通过 BLASTSyncEngine 将A B进程对应窗口容器的同步事务合入到 Merged 事务,Merged 事务包含了下一帧消费者的配置更改操作、A B进程的应用内容绘制数据、SystemUI 提交的原子操作。
- 系统服务将 Merged 事务传递回 systemUI 进程进行统一提交。