Android Framework(六)WMS-窗口显示流程——窗口内容绘制与显示
文章目录
- 窗口显示流程
- 明确目标
- 窗户内容绘制与显示流程
- 窗口Surface状态
- 完整流程图
- 应用端处理
- finishDrawingWindow 的触发
- system_service处理
- WindowState状态 -- COMMIT_DRAW_PENDING
- 本次layout 流程简述
窗口显示流程
目前窗口的显示到了最后一步。
在 addWindow 流程中,创建挂载了 WindowState
在 relayoutWindow 流程为这个窗口创建了 Surface 并且还计算好了这个窗口的大小和在屏幕上的位置,并把窗口的 Surface 状态设置为了 DRAW_PENDING 。
这一步执行完后,应用端就可以开始绘制 View 树了,绘制完成后,需要把内容显示到屏幕上,也就是现在这个 Surface ,这一步就是本篇分析的内容:finishDrawingWindow 流程。
明确目标
现在可以明确 finishDrawingWindow 流程目的只有1个:把窗口的内容显示到屏幕上。
这里可能会有几个疑问:
-
1、在 relayoutWindow 流程不是已经创建好 Surface 了吗?这一步目的是“把窗口的内容显示到屏幕上”,应用端拿到 Surface 绘制完 UI 等 VSync 来的时候上帧不就屏幕上有画面了?
-
2、难道应用端每一帧绘制完都需要走这个 finishDrawingWindow 流程吗?
回答问题之前,先看一个案例:屏幕旋转
在屏幕旋转的时候会应用窗口的改变,这一阶段手机屏幕的 UI 的很混乱的,为了提示用户体验,google 的做法是旋转之前截个屏,然后创建一个层级很高的图层显示这个截图。这样一来旋转期间,用户看到的就是截图的内容。
这里又有个疑问,既然是这样那为啥还有那么多旋转黑屏的问题?这是因为动画执行的时间是写在动画文件里的,并不是根据旋转逻辑执行完毕来的(我本以为是以这个为动画结束条件)。当然这不是当前重点。
所以现在看一下这个截图图层的代码逻辑:
# ScreenRotationAnimation
// 截屏图层SurfaceControl
private SurfaceControl mScreenshotLayer;
ScreenRotationAnimation(DisplayContent displayContent, @Surface.Rotation int originalRotation) {
......
// 拿到一个事务
final SurfaceControl.Transaction t = mService.mTransactionFactory.get();
try {
......
// 1. 创建截图的图层
String name = "RotationLayer";
mScreenshotLayer = displayContent.makeOverlay()
.setName(name)
......
.setBLASTLayer() // 设置为“Buff”图层
.build();
......
// 获取截图buff
GraphicBuffer buffer = GraphicBuffer.createFromHardwareBuffer(
screenshotBuffer.getHardwareBuffer());
......
t.setBuffer(mScreenshotLayer, buffer);// 2. 设置截图buff给截图的图层
t.setColorSpace(mScreenshotLayer, screenshotBuffer.getColorSpace());
// 3. 执行显示 Surface
t.show(mScreenshotLayer);
t.show(mBackColorSurface);
} ......
......
// 4. 事务提交
t.apply();
}
在上层控制一个 Surface 的显示分为4步:
- 1、创建 SurfaceControl
- 2、设置 buff 给SurfaceControl (显示内容)
- 3、执行 SurfaceControl.Transaction::show 传递这个 Surface
- 4、执行 SurfaceControl.Transaction::apply 通知 SurfaceFlinger
也就是说一个 Surface 最新显示到屏幕上是通过 SurfaceControl.Transaction 事务完成的,需要把显示 Surface 的要求加的事务中(show) 在执行事务的条件,这样 SurfaceFlinger 才会把显示对应的 Layer 。
其中 1,2 两步在 relayoutWindow 流程和应用端绘制 View 树就完成了,这个时候 Surface 下的 buff 是有内容的,但是还没有显示到屏幕上。所以要执行 finishDrawingWindow 流程来让 SurfaceFlinger 显示这个 Layer 。这也是第一个问题的答案。
第二个问题的答案是应用不需要每一帧绘制完都执行 finishDrawingWindow 流程,后面的应用上帧就是通过 Surface 内部的生产消费者模型完成。
窗户内容绘制与显示流程
如图整个流程可以分为以下几步:
- 1、应用端绘制后(
View绘制三部曲
)触发 finishDrawingWindow 流程 - 2、system_service 将窗口的 Surface 状态从原来是 DRAW_PENDING 更新到 COMMIT_DRAW_PENDING 表示准备提交
- 3、然后触发一次 layout 这次 layout 的目的是将这个窗口的 Surface 显示到屏幕上
- 3.1 状态设置到 READY_TO_SHOW 表示准备显示
- 3.2 状态设置到 HAS_DRAWN 表示已经显示在屏幕上
- 3.3 把这次 layout 对 Surface 的操作通过 SurfaceControl.Transaction 统一提交到 SurfaceFlinger
- 4、SurfaceFlinger 显示窗口的 Layer
窗口Surface状态
窗口Surface状态定义在 WindowStateAnimator.java 下面,结合源码的注释和实际场景简单解释一下各个状态:
# WindowStateAnimator
/** This is set when there is no Surface */
// 没有 Surface的时候,说明没有创建或者窗口销毁
static final int NO_SURFACE = 0;
/** This is set after the Surface has been created but before the window has been drawn. During
* this time the surface is hidden. */
// Surface 刚刚创建但是还没绘制的状态。 也就是 relayoutWindow 流程时设置的
static final int DRAW_PENDING = 1;
/** This is set after the window has finished drawing for the first time but before its surface
* is shown. The surface will be displayed when the next layout is run. */
// 窗口第一次完成绘制之后的状态,将在下一次 layout 的时候执行。
// 是等待提交到SF的状态
static final int COMMIT_DRAW_PENDING = 2;
/** This is set during the time after the window's drawing has been committed, and before its
* surface is actually shown. It is used to delay showing the surface until all windows in a
* token are ready to be shown. */
// 已经提交到SF, 准备显示到屏幕上
static final int READY_TO_SHOW = 3;
/** Set when the window has been shown in the screen the first time. */
// 窗口已经显示
static final int HAS_DRAWN = 4;
Surface状态的状态切换流程如下:
-
1、在 relayoutWindow 流程创建 Surface 后在 createSurfaceLocked 方法将状态设置为 DRAW_PENDING
-
2、应用绘制完成会后触发 finishDrawingWindow 方法,这个方法分为以下几步:
-
2.1 流程开始就通过 finishDrawingLocked 方法设置状态为 COMMIT_DRAW_PENDING
-
2.2 执行 requestTraversal 触发 layout 流程,这里可能对多次执行。相关的事情都在内部的 applySurfaceChangesTransaction 方法中处理
- 2.2.1 在 commitFinishDrawingLocked 方法把窗口状态设置为 READY_TO_SHOW
- 2.2.2 在 performShowLocked 方法把窗口状态设置为 HAS_DRAWN
- 2.2.3 执行 prepareSurfaces 方法,最终构建窗口 Surface 显示的事务
-
-
3、layout 流程 WindowManagerService::closeSurfaceTransaction 方法里会真正将事务提交到 SurfaceFlinger 处理
所以现在更明确的当前流程的主线任务:
1、找到设置 Surface 状态为 COMMIT_DRAW_PENDING、 READY_TO_SHOW 和 HAS_DRAWN 的地方。
2、找到执行 SurfaceControl.Transaction::show 和 SurfaceControl.Transaction::apply 执行的地方就完成了。
完整流程图
本篇的主要逻辑和上篇一样,也是在一次 layout 里,layout 几户覆盖了所有的窗口逻辑,非常复杂,这里只贴出关于窗口显示逻辑流程图:
应用端处理
既然是绘制完成后的处理,触发的地方还是应用端本身,只有应用端绘制完成了才会触发逻辑。
再看一下 ViewRootImpl::setView 的调用链:
ViewRootImpl::setView
ViewRootImpl::requestLayout
ViewRootImpl::scheduleTraversals
ViewRootImpl.TraversalRunnable::run -- Vsync相关--scheduleTraversals
ViewRootImpl::doTraversal
ViewRootImpl::performTraversals
ViewRootImpl::relayoutWindow
Session::relayout -- 第二步:relayoutWindow
ViewRootImpl::updateBlastSurfaceIfNeeded
Surface::transferFrom -- 应用端Surface赋值
ViewRootImpl::performMeasure -- View绘制三部曲 --Measure
ViewRootImpl::performLayout -- View绘制三部曲 --Layout
ViewRootImpl::createSyncIfNeeded
SurfaceSyncGroup::init
ViewRootImpl::reportDrawFinished
Session::finishDrawing -- 第三步:finishDrawingWindow
ViewRootImpl::performDraw -- View绘制三部曲 --Draw
SurfaceSyncGroup::markSyncReady -- 触发绘制完成回调
Session.addToDisplayAsUser -- 第一步:addWindow
前面分析【relayoutWindow流程】的时候已经分析过 ViewRootImpl::performTraversals 方法了,不过当前重点不一样,所以还需要再看一遍这个方法(增加了一些当前流程相关的代码)
- 1、后续需要介绍软绘硬绘的流程,所以可以看到硬绘的初始化逻辑也在这个方法
- 2、relayoutWindow 相关
- 3、经过第二步 relayoutWindow 后 View 就可以绘制了
- 4、绘制完成后就要通知 SurfaceFlinger 进行合成了,也就是本篇分析的 finishDrawing 流程
当前分析 finishDrawing 流程,首先可以看到 relayoutWindow 方法执行后,会触发3个View绘制的方法,也就是常说的 View 绘制三部曲:measure、layout、draw。
但是这里有个奇怪的地方: “4.1 createSyncIfNeeded” 方法是触发 finishDrawingWindow 的,但是这个方法在 “3.3 performDraw”的上面。
这是因为代码的顺序不代表真正的执行顺序,这里的“4.1 createSyncIfNeeded”只是设置了“回调”,等时机到了就会触发执行,而这个时机就是 View 绘制完成后,在 “4.2 markSyncReady 触发”
这一部分的逻辑有点绕,不过目前分析的是主流程,所以这块逻辑以上的描述当黑盒理解这段的调用: View 绘制结束后就会在 4.2 出触发 4.1 内部的执行,进入触发 finishDrawingWindow 流程即可。
这部分的代码 U 做了重构,后面再单独写一篇详细解释直接的调用逻辑。
finishDrawingWindow 的触发
在 ViewRootImpl::performTraversals 方法最后会执行 SurfaceSyncGroup::markSyncReady 方法,最终会触发 ViewRootImpl::createSyncIfNeeded 方法下的 ViewRootImpl::reportDrawFinished 来真正 finishDrawingWindow 流程。
# ViewRootImpl
// 创建对象
private SurfaceSyncGroup mActiveSurfaceSyncGroup;
// 是否有同步的内容需要上报
boolean mReportNextDraw;
private void createSyncIfNeeded() {
// 如果已经在本地进行同步或者没有需要同步的内容
// mReportNextDraw 变量也是控制每一帧绘制完不都要执行 finishDrawingWindow 流程的原因
if (isInWMSRequestedSync() || !mReportNextDraw) {
return;
}
// 获取当前同步序列号
final int seqId = mSyncSeqId;
// 传入一个匿名类
mWmsRequestSyncGroupState = WMS_SYNC_PENDING;
mWmsRequestSyncGroup = new SurfaceSyncGroup("wmsSync-" + mTag, t -> {
// 合并传入的transaction到mSurfaceChangedTransaction中
mWmsRequestSyncGroupState = WMS_SYNC_MERGED;
// 重点* 报告绘制完成,传入之前获取的序列号
reportDrawFinished(t, seqId);
});
if (DEBUG_BLAST) {
// 打印日志
Log.d(mTag, "Setup new sync=" + mWmsRequestSyncGroup.getName());
}
// 将mSyncTarget添加到mSyncId对应的同步中
mWmsRequestSyncGroup.add(this, null /* runnable */);
}
这个方法的重点就是在应用绘制完成后触发 finishDrawingWindow 流程,也就是触发 ViewRootImpl::reportDrawFinished 方法。
# ViewRootImpl
private void reportDrawFinished(@Nullable Transaction t, int seqId) {
// 日志和Trace相关
if (DEBUG_BLAST) {
Log.d(mTag, "reportDrawFinished");
}
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.instant(Trace.TRACE_TAG_VIEW, "reportDrawFinished " + mTag + " seqId=" + seqId);
}
try {
// 重点* finishDrawing流程
mWindowSession.finishDrawing(mWindow, t, seqId);
......
} ......
......
}
# Session
@Override
public void finishDrawing(IWindow window,
@Nullable SurfaceControl.Transaction postDrawTransaction, int seqId) {
if (DEBUG) Slog.v(TAG_WM, "IWindow finishDrawing called for " + window);
if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "finishDrawing: " + mPackageName);
}
// 触发WMS 执行finishDrawingWindow 流程
mService.finishDrawingWindow(this, window, postDrawTransaction, seqId);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
这个方法的重点就是在应用绘制完成后触发 finishDrawingWindow 流程,也就是触发 ViewRootImpl::reportDrawFinished 方法。
# ViewRootImpl
private void reportDrawFinished(@Nullable Transaction t, int seqId) {
// 日志和Trace相关
if (DEBUG_BLAST) {
Log.d(mTag, "reportDrawFinished");
}
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.instant(Trace.TRACE_TAG_VIEW, "reportDrawFinished " + mTag + " seqId=" + seqId);
}
try {
// 重点* finishDrawing流程
mWindowSession.finishDrawing(mWindow, t, seqId);
......
} ......
......
}
# Session
@Override
public void finishDrawing(IWindow window,
@Nullable SurfaceControl.Transaction postDrawTransaction, int seqId) {
if (DEBUG) Slog.v(TAG_WM, "IWindow finishDrawing called for " + window);
if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "finishDrawing: " + mPackageName);
}
// 触发WMS 执行finishDrawingWindow 流程
mService.finishDrawingWindow(this, window, postDrawTransaction, seqId);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
唯一做的一件事就是跨进程触发 WindowManagerService::finishDrawingWindow 。 到这里应用端的事情就处理完了,后面的流程在 system_service 进程。
system_service处理
system_service 处理主要的调用链整理如下:
WindowManagerService::finishDrawingWindow
WindowState::finishDrawing
WindowStateAnimator::finishDrawingLocked -- COMMIT_DRAW_PENDING
WindowPlacerLocked::requestTraversal -- 触发layout
Traverser::run
WindowSurfacePlacer::performSurfacePlacement
WindowSurfacePlacer::performSurfacePlacementLoop
RootWindowContainer::performSurfacePlacement -- 开始layout逻辑
RootWindowContainer::performSurfacePlacementNoTrace
WindowManagerService::openSurfaceTransaction -- 打开Surface事务
RootWindowContainer::applySurfaceChangesTransaction -- 处理Surface事务
DisplayContent::applySurfaceChangesTransaction -- 遍历每个屏幕
DisplayContent::performLayout -- relayoutWinodw 流程
DisplayContent::forAllWindows -- 每个窗口执行mApplySurfaceChangesTransaction
WindowStateAnimator::commitFinishDrawingLocked -- READY_TO_SHOW
WindowState::performShowLocked -- HAS_DRAWN
DisplayContent::prepareSurfaces -- Surface 处理
WindowContainer::prepareSurfaces -- 遍历每个孩子
WindowState::prepareSurfaces -- 忽略其他,只看窗口的实现
WindowStateAnimator::prepareSurfaceLocked
WindowSurfaceController::showRobustly
WindowSurfaceController::setShown
SurfaceControl.Transaction::show -- Surface显示
WindowManagerService::closeSurfaceTransaction -- 处理关闭Surface事务
SurfaceControl::closeTransaction
GlobalTransactionWrapper::applyGlobalTransaction
GlobalTransactionWrapper::nativeApplyTransaction -- 触发native
# WindowManagerService
final WindowSurfacePlacer mWindowPlacerLocked;
void finishDrawingWindow(Session session, IWindow client,
@Nullable SurfaceControl.Transaction postDrawTransaction, int seqId) {
if (postDrawTransaction != null) {
postDrawTransaction.sanitize(Binder.getCallingPid(), Binder.getCallingUid());
}
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
// 获取到对应的WindowState
WindowState win = windowForClientLocked(session, client, false);
// T版本这里是个 proto日志
Slog.w(TAG, "finishDrawingWindow: "+win+" mDrawState="
+(win != null ? win.mWinAnimator.drawStateToString() : "null"));
// 重点* 1. 执行WindowState::finishDrawing
if (win != null && win.finishDrawing(postDrawTransaction, seqId)) {
if (win.hasWallpaper()) {
win.getDisplayContent().pendingLayoutChanges |=
WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
}
// 将当前WindowState.mLayoutNeeded置为true
win.setDisplayLayoutNeeded();
// 重点* 2. 请求进行布局刷新
mWindowPlacerLocked.requestTraversal();
}
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
system_service 进程第一个处理的方法就是 WindowManagerService::finishDrawingWindow 这个方法也就做了2件事:
- 1、WindowState::finishDrawing 将 Surface 状态设置为 COMMIT_DRAW_PENDING
- 2、WindowSurfacePlacer::requestTraversal 框架层的 layout ,将状态设置为 READY_TO_SHOW ,HAS_DRAWN ,然后通知到 SurfaceFlinger
这里有上述的2个流程需要分析,首先会执行 WindowState::finishDrawing ,将WindowState状态设置为 COMMIT_DRAW_PENDING ,表示应用端已经绘制完成了,可以提交给SF了。
第一步操作完之后,就会执行 WindowSurfacePlacer::requestTraversal ,这个方法是执行一次 layout 逻辑。
在前面看窗口状态 COMMIT_DRAW_PENDING 定义的时候,google 注释提过: “会下一次 layout 的时候显示到屏幕上”,指的就是在这里触发的 layout。
在第二步 layout 的时候会遍历每个窗口,目前只关心当前分析的场景的这个窗口,在这次 layout 会做3件事:
- 1、将窗口状态设置为 READY_TO_SHOW
- 2、将窗口状态设置为 HAS_DRAWN
- 3、通过 SurfaceControl.Transaction 通知 SurfaceFlinger 做显示合成
下面开始在代码中梳理流程。
WindowState状态 – COMMIT_DRAW_PENDING
# WindowState
final WindowStateAnimator mWinAnimator;
boolean finishDrawing(SurfaceControl.Transaction postDrawTransaction, int syncSeqId) {
......
// 主流程
final boolean layoutNeeded =
mWinAnimator.finishDrawingLocked(postDrawTransaction, mClientWasDrawingForSync);
mClientWasDrawingForSync = false;
// We always want to force a traversal after a finish draw for blast sync.
return !skipLayout && (hasSyncHandlers || layoutNeeded);
}
主要是执行了 WindowStateAnimator::finishDrawingLocked ,内部会将 WindowState 的状态设置为 COMMIT_DRAW_PENDING ,这个是非常重要的一步。
# WindowStateAnimator
boolean finishDrawingLocked(SurfaceControl.Transaction postDrawTransaction,
boolean forceApplyNow) {
......
// 只有当前状态是DRAW_PENDING的时候才可以走进逻辑
if (mDrawState == DRAW_PENDING) {
ProtoLog.v(WM_DEBUG_DRAW,
"finishDrawingLocked: mDrawState=COMMIT_DRAW_PENDING %s in %s", mWin,
mSurfaceController);
if (startingWindow) {
// 如果是StartingWindow还有专门的log
ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Draw state now committed in %s", mWin);
}
mDrawState = COMMIT_DRAW_PENDING;
// 表示需要 layout
layoutNeeded = true;
}
......
}
这样第一步就执行完了,流程很简单,只是设置窗口状态为 COMMIT_DRAW_PENDING 。
本次layout 流程简述
上一小节只是改了状态,下一个状态是 READY_TO_SHOW ,前面看到google对它有一个注释:The surface will be displayed when the next layout is run.
也就是说在下一次 layout 会触发 Surface 的显示,所以关键流程还是在 “next layout”,
那什么是 “next layout” ?
我们知道屏幕上有任何风吹操作都会触发一次 layout 流程,主要就是执行 WindowSurfacePlacer::performSurfacePlacement 这就是 一次 layout 。
WindowPlacerLocked::requestTraversal 触发的 layout 流程就是之前 relayoutWindow 流程看到的 WindowSurfacePlacer::performSurfacePlacement 。这个流程触发的地方非常多,只是当前 finishDrawingWindow 会主动触发一次罢了。对于这种高频率触发的方法,需要留意一下,初学者知道每个主流程会走什么逻辑就好,慢慢的随着知识体系的构建,再看这个流程其实就没那么复杂了。
WindowSurfacePlacer::performSurfacePlacement 的逻辑会遍历屏幕上每一个窗口,然后让其根据最新情况做对应的处理,比如 relayoutWinodw 流程的时候就会遍历到窗口做
执行 computeFrames 计算窗口大小。
当前分析的场景自然也会遍历窗口,触发这次 layout 的目的就是让当前这个窗口的 Surface 提交到 SurfaceFlinger 。
这个流程之前看过了,所以直接从 RootWindowContainer::performSurfacePlacement 方法开始
# RootWindowContainer
// 这个方法加上了trace
void performSurfacePlacement() {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "performSurfacePlacement");
try {
performSurfacePlacementNoTrace();
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
// 主要干活的还是这个
void performSurfacePlacementNoTrace() {
......
// Trace
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applySurfaceChanges");
// 开启Surface事务
mWmService.openSurfaceTransaction();
try {
// 重点* 1. 处理Surface事务
applySurfaceChangesTransaction();
} catch (RuntimeException e) {
Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
} finally {
// 关闭Surface事务
mWmService.closeSurfaceTransaction("performLayoutAndPlaceSurfaces");
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
......
// 重点* 2. 处理App事务
checkAppTransitionReady(surfacePlacer);
......
}