假設有這樣一個場景,一個是應用主窗口,一個是帶有內容的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<WindowContainer> haveConfigChanges = new ArraySet<>();
Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
t.getChanges().entrySet().iterator();
while (entries.hasNext()) {
final Map.Entry<IBinder, WindowContainerTransaction.Change> 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<IBinder, WindowContainerTransaction.Change> 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 進程進行統一提交。