忽然有一天,我想要做一件事:去代码中去验证那些曾经被"灌输"的理论。
-- 服装学院的IT男
一个应用想要将它的UI内容显示到屏幕窗口上,涉及到3个模块: 应用端,SystemService端和SurfaceFlinger端。
在App开发中一个View想要显示需要经过3个步骤,也就是View三部曲:Measure,Layout,Draw 对应的一个Window想要在屏幕上显示也需要经过3个步骤:
-
- addWindow : SystemService端为应用窗口创建对应的WindowState并且挂载到窗口树中
-
- relayoutWindow : 这一步会创建一个Surface返回给应用端进行绘制,并且触发WMS的各个窗口的位置摆放和窗口尺寸计算(relayout)
-
- finishDrawingWindow:这一步是应用端的View绘制完成后,Surface已经有UI信息了,需要通过SurfaceFlinger进行合成
经过前面2步,现在 WMS 中已经有了应用的 WindowState 并且也计算好了窗口的大小,同时还创建了对应的 Surface 供应用 View 进行绘制。
接下来应用端会开始绘制,应用端的 View 绘制完成后就需要显示到屏幕上了,这就需要告知 SurfaceFlinger 进行合成处理了。
下面徐要分析的窗口显三部曲的最后一步:finishDrawingWindow 。
本篇的目的就是梳理 FrameWork 层是如何通知 SurfaceFlinger 应用窗口已经绘制完成。
有2个线索:
-
- FrameWork 中对窗口(WindowState)定义了不同的状态,找到对应状态切换的地方。
-
- FrameWork 通知 SF 做操作,是需要通过 SurfaceControl.Transaction 完成的
-
- 首先看到是应用端完成了绘制,通知到 system_service 来做后续处理
-
- 然后WMS执行 finishDrawingWindow 方法开始处理,处理的事情可以分为2块
- 2.1 窗口状态的处理
- 2.2 构建出一个 SurfaceControl.Transaction 用来通知 SurfaceFlinger 显示这个窗口的 Surface
-
- SurfaceFlinger 做后续的处理
这里提到的窗口状态定义在 WindowStateAnimator.java 下面,结合源码的注释和实际场景简单解释一下各个状态:
php
# 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;
大概流程如下:
-
- 在上一步 relayoutWindow 的时候状态 WindowState 状态已经被设置为 DRAW_PENDING
-
- 应用绘制完成会后触发 finishDrawingWindow 方法,这个方法分为2步
- 2.1 执行 finishDrawing 方法将窗口状态设置为 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 显示的事务,通知 SurfaceFlinger 做后续的处理
开始撸代码。
1. 应用端处理
首先既然是绘制完成后的处理,触发的地方还是应用端本身,只有应用端绘制完成了才会触发逻辑。
再看一下 ViewRootImpl::setView 的调用链:
arduino
ViewRootImpl::setView
ViewRootImpl::requestLayout
ViewRootImpl::scheduleTraversals
ViewRootImpl.TraversalRunnable::run --- Vsync相关--scheduleTraversals
ViewRootImpl::doTraversal
ViewRootImpl::performTraversals
ViewRootImpl::relayoutWindow --- 第二步:relayoutWindow
Session::relayout --- 跨进程调用
ViewRootImpl::updateBlastSurfaceIfNeeded
Surface::transferFrom --- 应用端Surface赋值
ViewRootImpl::performMeasure --- View绘制三部曲 --Measure
ViewRootImpl::performLayout --- View绘制三部曲 --Layout
ViewRootImpl::createSyncIfNeeded --- 第三步:绘制完成 finishDrawingWindow
SurfaceSyncer::setupSync
SyncSet::init --- 将回调封装在SyncSet下
ViewRootImpl::reportDrawFinished -- 等待回调,触发finishDrawingWindow
Session::finishDrawing -- 触发system_service进程执行finishDrawing流程 -- 开始跨进程(finishDrawing流程)
ViewRootImpl::performDraw --- View绘制三部曲 --Draw
SurfaceSyncer::markSyncReady --- 触发:finishDrawingWindow
Session.addToDisplayAsUser --- 第一步:addWindow
前面分析relayoutWindow流程 的时候已经分析过ViewRootImpl::performTraversals 方法了,不过当前重点不一样,所以还需要再看一遍这个方法(增加了一些当前流程相关的代码)
ini
# ViewRootImpl
// 创建对象
private final SurfaceSyncer mSurfaceSyncer = new SurfaceSyncer();
// 是否有同步的内容需要上报
boolean mReportNextDraw;
private void performTraversals() {
......
// mWinFrame保存的是当前窗口的尺寸
Rect frame = mWinFrame;
----1.1 硬绘相关----
// 硬件加速是否初始化
boolean hwInitialized = false;
......
----2. relayoutWindow流程----
// 内部会将经过WMS计算后的窗口尺寸给mWinFrame
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
......
// 1.2 初始化硬件加速,将Surface与硬件加速绑定
hwInitialized = mAttachInfo.mThreadedRenderer.initialize(mSurface);
......
---- 3.1 View绘制三部曲--Measure
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
......3.2 View绘制三部曲--Layout
performLayout(lp, mWidth, mHeight);
......
// 设置 mReportNextDraw = true,表示当前需要上报SurfaceFlinger
if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
reportNextDraw(); // 打开应用的时候触发上报
}
----4.1 finishDrawing流程----
createSyncIfNeeded();
...... 3.3 View绘制三部曲--Draw
if (!performDraw() && mSyncBufferCallback != null) {
mSyncBufferCallback.onBufferReady(null);
}
......
// 4.2 触发执行回调
mReportNextDraw = false;
if (isInLocalSync()) {
mSurfaceSyncer.markSyncReady(mSyncId);
mSyncId = UNSET_SYNC_ID;
}
......
}
-
- 后续需要介绍软绘硬绘的流程,所以可以看到硬绘的初始化逻辑也在这个方法
-
- relayoutWindow 相关
-
- 经过第二步 relayoutWindow 后 View 就可以绘制了
-
- 绘制完成后就要通知 SurfaceFlinger 进行合成了,也就是本篇分析的 finishDrawing 流程
当前分析 finishDrawing 流程,首先可以看到 relayoutWindow 方法执行后,会触发3个View绘制的方法,也就是常说的 View 绘制三部曲,但是这里有个奇怪的地方: "4.1 createSyncIfNeeded" 方法是触发 finishDrawingWindow 的,但是这个方法在 "3.3 performDraw"的上面。
这是因为代码的顺序不代表真正的执行顺序,这里的"4.1 createSyncIfNeeded"只是设置了"回调",等时机到了就会触发执行,而这个时机就是 View 绘制完成后,在 "4.2 markSyncReady触发"
这一部分的逻辑有点绕,不过目前分析的是主流程,所以这块逻辑以上的描述当黑盒理解这段的调用: View绘制结束后就会在 4.2 出触发 4.1 内部的执行,进入触发 finishDrawingWindow 流程即可。
后面再单独写一篇详细解释直接的调用逻辑。
1.2 finishDrawingWindow 的触发
在 ViewRootImpl::performTraversals 方法最后会执行 SurfaceSyncer::markSyncReady 方法,最终会触发 ViewRootImpl::createSyncIfNeeded 方法下的 ViewRootImpl::reportDrawFinished 来真正 finishDrawingWindow 流程。
scss
# ViewRootImpl
// 创建对象
private final SurfaceSyncer mSurfaceSyncer = new SurfaceSyncer();
// 是否有同步的内容需要上报
boolean mReportNextDraw;
private void createSyncIfNeeded() {
// 如果已经在本地进行同步或者没有需要同步的内容
if (isInLocalSync() || !mReportNextDraw) {
return;
}
// 获取当前同步序列号
final int seqId = mSyncSeqId;
// 调用mSurfaceSyncer::setupSync,并传入一个匿名类
mSyncId = mSurfaceSyncer.setupSync(transaction -> {
mHandler.postAtFrontOfQueue(() -> {
// 合并传入的transaction到mSurfaceChangedTransaction中
mSurfaceChangedTransaction.merge(transaction);
// 重点* 报告绘制完成,传入之前获取的序列号
reportDrawFinished(seqId);
});
});
if (DEBUG_BLAST){
// 打印日志
Log.d(mTag, "Setup new sync id=" + mSyncId);
}
// 将mSyncTarget添加到mSyncId对应的同步中
mSurfaceSyncer.addToSync(mSyncId, mSyncTarget);
notifySurfaceSyncStarted();
}
ViewRootImpl::reportDrawFinished 方法如下:
typescript
# ViewRootImpl
private void reportDrawFinished(int seqId) {
// 日志和Trace相关
if (DEBUG_BLAST) {
Log.d(mTag, "reportDrawFinished " + Debug.getCallers(5));
}
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "finish draw");
}
try {
// 重点* finishDrawing流程
mWindowSession.finishDrawing(mWindow, mSurfaceChangedTransaction, seqId);
......
} ......
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
# Session
@Override
public void finishDrawing(IWindow window,
@Nullable SurfaceControl.Transaction postDrawTransaction, int seqId) {
if (DEBUG) Slog.v(TAG_WM, "IWindow finishDrawing called for " + window);
mService.finishDrawingWindow(this, window, postDrawTransaction, seqId);
}
唯一做的一件事就是跨进程触发 WindowManagerService::finishDrawingWindow 。 到这里应用端的事情就处理完了,后面的流程在 system_service 进程。
2. system_service处理
结合文章上面看到的流程图,这里也先把代码主要的调用链整理出来:
php
WindowManagerService::finishDrawingWindow
WindowState::finishDrawing
WindowStateAnimator::finishDrawingLocked -- COMMIT_DRAW_PENDING
WindowPlacerLocked::requestTraversal -- 触发layout (可能会执行多次)
Traverser::run -- Runnable异步执行
WindowSurfacePlacer::performSurfacePlacement
WindowSurfacePlacer::performSurfacePlacementLoop
WindowSurfacePlacer::performSurfacePlacementLoop
RootWindowContainer::performSurfacePlacement -- 开始逻辑
RootWindowContainer::performSurfacePlacementNoTrace
WindowManagerService::openSurfaceTransaction -- 打开Surface事务
RootWindowContainer::applySurfaceChangesTransaction -- 处理Surface事务
DisplayContent::applySurfaceChangesTransaction
DisplayContent::performLayout -- relayoutWinodw 流程
DisplayContent::mApplySurfaceChangesTransaction
WindowStateAnimator::commitFinishDrawingLocked -- READY_TO_SHOW
WindowState::performShowLocked -- HAS_DRAWN(一般第二次菜进入)
ActivityRecord::updateDrawnWindowStates
mTmpUpdateAllDrawn::add -- 存入
mTmpUpdateAllDrawn::removeLast -- 取出
ActivityRecord::updateAllDrawn -- allDrawn = true
DisplayContent.setLayoutNeeded -- 再来一次layout
DisplayContent::prepareSurfaces -- Surface 处理
WindowContainer::prepareSurfaces -- 遍历每个孩子
WindowState::prepareSurfaces -- 忽略其他,只看窗口的实现
WindowStateAnimator::prepareSurfaceLocked
WindowStateAnimator::showSurfaceRobustlyLocked
WindowSurfaceController::showRobustly
WindowSurfaceController::setShown
SurfaceControl.Transaction::show -- Surface显示
WindowManagerService::closeSurfaceTransaction -- 处理关闭Surface事务
SurfaceControl::closeTransaction
GlobalTransactionWrapper::applyGlobalTransaction
SurfaceControl::nativeApplyTransaction -- 触发native
现在开始撸代码:
java
# WindowManagerService
final WindowSurfacePlacer mWindowPlacerLocked;
void finishDrawingWindow(Session session, IWindow client,
@Nullable SurfaceControl.Transaction postDrawTransaction, int seqId) {
if (postDrawTransaction != null) {
postDrawTransaction.sanitize();
}
final long origId = Binder.clearCallingIdentity();
try {
synchronized (mGlobalLock) {
// 获取到对应的WindowState
WindowState win = windowForClientLocked(session, client, false);
// log
ProtoLog.d(WM_DEBUG_ADD_REMOVE, "finishDrawingWindow: %s mDrawState=%s",
win, (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件事:
-
- WindowState::finishDrawing 当前分析的主要流程,将状态设置为 COMMIT_DRAW_PENDING
-
- 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件事:
-
- 将窗口状态设置为 READY_TO_SHOW
-
- 将窗口状态设置为 HAS_DRAWN
-
- 执行 SurfaceControl.Transaction 通知 SurfaceFlinger 做显示合成
下面开始在代码中梳理流程。
2.1 WindowState状态 -- COMMIT_DRAW_PENDING
arduino
# 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 ,这个是非常重要的一步。
typescript
# 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 。
2.2 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 计算窗口大小。
当前分析的场景自然也会遍历窗口,正常逻辑下就是让目标窗口执行完 finishDrawingWindow 流程。
这个流程之前看过了,现在再完整看一遍 layout 流程,会忽略更多无关的的代码。
java
# WindowSurfacePlacer
private final Traverser mPerformSurfacePlacement = new Traverser();
private class Traverser implements Runnable {
@Override
public void run() {
synchronized (mService.mGlobalLock) {
performSurfacePlacement();
}
}
}
void requestTraversal() {
......
mService.mAnimationHandler.post(mPerformSurfacePlacement);
}
可以看到 WindowPlacerLocked::requestTraversal 其实就是触发了 WindowSurfacePlacer::performSurfacePlacement 方法的调用。
需要注意这边的一个异步的 Runnable 所以打堆栈或打 Trace 的时候需要留意一下
arduino
# WindowSurfacePlacer
final void performSurfacePlacement() {
performSurfacePlacement(false /* force */);
}
// 控制是否需要继续执行 performSurfacePlacementLoop方法
private boolean mTraversalScheduled;
final void performSurfacePlacement(boolean force) {
if (mDeferDepth > 0 && !force) {
mDeferredRequests++;
return;
}
// 最大次数循环为6次
int loopCount = 6;
do {
// 设置为false
mTraversalScheduled = false;
// 重点方法
performSurfacePlacementLoop();
mService.mAnimationHandler.removeCallbacks(mPerformSurfacePlacement);
loopCount--;
} while (mTraversalScheduled && loopCount > 0);
mService.mRoot.mWallpaperActionPending = false;
}
这个方法在 relayoutWindow 也详细讲过了, 主要看 performSurfacePlacementLoop
csharp
# WindowSurfacePlacer
private void performSurfacePlacementLoop() {
......
// 重点* 对所有窗口执行布局操作
mService.mRoot.performSurfacePlacement();
......
}
上面都是一些执行的条件处理,真正的处理还是从 RootWindowContainer::performSurfacePlacement 方法开始
scss
# 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);
......
}
这里就有2个主要流程,也是2个类型的事务处理:
-
- applySurfaceChangesTransaction 方法,执行 Surface 事务
可以看到这行代码前后有 SurfaceTransaction 的打开和关闭,那说明这里的逻辑是会触发 Surface 操作的。
applySurfaceChangesTransaction 方法内部做很多事,比如上一篇的 layoutWindow 流程,当前分析的场景在这个方法里会执行将窗口状态设置为 READY_TO_SHOW ,并且构建一个 Surface 事务来显示当前窗口的 Surface 。
然后会在 WindowManagerService::closeSurfaceTransaction 方法中触发 SurfaceTransaction 的apply 把 Surface 操作的事务提交到 SurfaceFlinger 。
-
- checkAppTransitionReady 方法,执行 App 事务 这个方法也是很常见并且核心的,Framework 层专门定义了 AppTransition 来表示一些 APP 的事务,根据用户具体的操作执行对应的 App 事务。如果事务已经满足执行条件,则会触发对应的 AppTransition 执行,然后也有一些窗口动画的触发。
这个方法知道一下即可,当前流程可以不分析。
2.3 applySurfaceChangesTransaction 方法概览
ini
# RootWindowContainer
private void applySurfaceChangesTransaction() {
......
// 遍历每个屏幕
final int count = mChildren.size();
for (int j = 0; j < count; ++j) {
final DisplayContent dc = mChildren.get(j);
dc.applySurfaceChangesTransaction();
}
......
}
不考虑多个屏幕的场景
java
# DisplayContent
// 需要更少是否已经绘制的集合
private final LinkedList<ActivityRecord> mTmpUpdateAllDrawn = new LinkedList();
void applySurfaceChangesTransaction() {
......
// 新的执行,清除数据
mTmpUpdateAllDrawn.clear();
......
// 重点* 1. layoutWindow 流程
performLayout(true /* initial */, false /* updateInputWindows */);
......
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "applyWindowSurfaceChanges");
try {
// 重点* 2. 遍历所有窗口执行 lambda表达式
forAllWindows(mApplySurfaceChangesTransaction, true /* traverseTopToBottom */);
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
// 重点* 3. Surface 操作
prepareSurfaces();
......
// 如果有需要更新的ActivityRecord则处理
while (!mTmpUpdateAllDrawn.isEmpty()) {
final ActivityRecord activity = mTmpUpdateAllDrawn.removeLast();
// See if any windows have been drawn, so they (and others associated with them)
// can now be shown.
// 内部会设置这个ActivityRecord 下的 allDrawn 为true
activity.updateAllDrawn();
}
}
这里有有个3重要的流程:
-
- relayoutWinodw 流程计算窗口大小(已经分析过,当前不管)
-
- 遍历每个窗口,执行 mApplySurfaceChangesTransaction 这个 lambda表达式 ,当前分析的场景是会把目标窗口状态设置为 READY_TO_SHOW
-
- Framework 层对Surface 的操作,当前场景就是会提交一个 Surface 显示的事务
然后还有一个重要的数据结构:mTmpUpdateAllDrawn
这个集合存储的是这次 layout 执行到当前 applySurfaceChangesTransaction 方法时,哪些 ActivityRecord 需要更新 allDrawn 属性了。
ActivityRecord 下面的这个 allDrawn 变量表示当前 ActivityRecord 下面的窗口是否全部绘制。
执行方法前会先把 mTmpUpdateAllDrawn 清空,然后在方法末尾遍历是否有元素。 那这个集合的元素是在哪里添加的呢?
在每个 WindowState 执行 mApplySurfaceChangesTransaction 时,如果符合条件就会加入集合。
2.4 WindowState状态 -- READY_TO_SHOW
arduino
forAllWindows(mApplySurfaceChangesTransaction, true /* traverseTopToBottom */);
这句代码会从上到下遍历每个窗口,然后执行 lambda 表达式, mApplySurfaceChangesTransaction 的定义如下
java
# DisplayContent
private final Consumer<WindowState> mApplySurfaceChangesTransaction = w -> {
......
final WindowStateAnimator winAnimator = w.mWinAnimator;
......
// 判断当前是否有Surface
if (w.mHasSurface) {
// Take care of the window being ready to display.
// 重点 * 1. 主流程设置为 READY_TO_SHOW
final boolean committed = winAnimator.commitFinishDrawingLocked();
......
}
......
// 重点 * 2. allDrawn 相关逻辑
// 拿到这个WindowState所属的ActivityRecord
final ActivityRecord activity = w.mActivityRecord;
// 已经请求可见
if (activity != null && activity.isVisibleRequested()) {
activity.updateLetterboxSurface(w);
// 更新绘制状态
final boolean updateAllDrawn = activity.updateDrawnWindowStates(w);
if (updateAllDrawn && !mTmpUpdateAllDrawn.contains(activity)) {
// 符合条件加入集合
mTmpUpdateAllDrawn.add(activity);
}
}
w.updateResizingWindowIfNeeded();
};
这里有2个逻辑需要注意:
-
- 主流程,将状态设置成 READY_TO_SHOW
-
- allDrawn 属性逻辑
当前还是先看 READY_TO_SHOW 流程,allDrawn相关的后面单独梳理。
mHasSurface 在relayoutWindow 流程创建 Surface 时设置为true,表示当前windowState的是否有 Surface 。 然后就调用其 WindowStateAnimator::commitFinishDrawingLocked 。
csharp
# WindowStateAnimator
boolean commitFinishDrawingLocked() {
......
// 1. 当前状态的判断,不满足则return
if (mDrawState != COMMIT_DRAW_PENDING && mDrawState != READY_TO_SHOW) {
return false;
}
// 2. 日志
ProtoLog.i(WM_DEBUG_ANIM, "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW %s",
mSurfaceController);
// 3. 设置状态
mDrawState = READY_TO_SHOW;
......
// 4. 系统Window或者StartWindow则会走后续流程设置为 HAS_DRAW
if (activity == null || activity.canShowWindows()
|| mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
result = mWin.performShowLocked();
}
return result;
......
}
这里有4个点,其中后面2个很重要:
-
- 这个方法每次 layout 的时候都可能会执行过来,所以要判断当前窗口的状态是否符合条件执行后面的逻辑
-
- 这个日志是上层分析黑屏问题看状态状态的关键
-
- 窗口状态设置成 READY_TO_SHOW ,这也是当前分析的主要里程碑
-
- 满足3个条件之一就会执行下一步,将窗口状态设置为 HAS_DRAW
- 4.1 不依赖 Activity 的窗口,一般是状态栏导航栏这种系统窗口,或者应用启动的悬浮窗
- 4.2 ActivityRecord::canShowWindows 是否可以显示窗口。这个方法的返回值又受3个因素影响,下一小节解释。
- 4.3 窗口类型为 StartWindow
我们当前分析场景是不是系统窗口和StartWindow,不过是符合了第二个条件的,详细看下一节。
2.5 WindowState状态 -- HAS_DRAW
现在执行到 WindowStateAnimator::commitFinishDrawingLocked 方法,窗口状态已经为 READY_TO_SHOW 了,离最终状态还差一步。 根据上面代码可知,只要条件符合就会进入执行 WindowState::performShowLocked 完整 HAS_DRAW 的设置,根据这3个条件,当前分析的是应用窗口,如果要进入肯定是第二个 ActivityRecord::canShowWindows 返回true 。
先看一下这个方法。
scss
# ActivityRecord
/**
* @return Whether we are allowed to show non-starting windows at the moment. We disallow
* showing windows during transitions in case we have windows that have wide-color-gamut
* color mode set to avoid jank in the middle of the transition.
*/
boolean canShowWindows() {
return allDrawn && !(isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION)
&& hasNonDefaultColorWindow());
}
根据注释:方法返回当前是否允许显示非启动窗口。不允许在转换过程中显示窗口,以防我们有宽色域的窗口颜色模式设置为避免在过渡中间出现jank。
感觉似懂非懂还是直接看具体代码的3个条件吧:
-
- allDrawn :当前ActivityRecord 下的所有窗口都已经绘制
-
- 第二个条件是窗口没在做动画(除StartWindow外)
-
- 没有设置非默认颜色模式的窗口 (不知道啥意思,不过正常都为true)
如果因为这个方法返回 false 导致没有走到设置 HAS_DRAW 状态,则可以 debug 一下是哪个值异常了,具体情况具体分析。
其中比较常见会导致返回 false 的一般是 allDrawn 这边变量导致的。 这边变量定义在 ActivityRecord 下,表示当前 ActivityRecord 下的所有窗口 是否都绘制完成并准备显示。
2.5.1 allDrawn 属性逻辑
首先 layout 会触发 RootWindowContainer::performSurfacePlacement 方法的调用,而且我们知道一次 layout 内部可能会执行多次 RootWindowContainer::performSurfacePlacement
以我当前的源码版本,这个逻辑大部分情况都是执行2次 (刷过android 14的版本,执行了一次)
来完整看一下这边变量的设置逻辑
scss
# DisplayContent
// 需要更少是否已经绘制的集合
private final LinkedList<ActivityRecord> mTmpUpdateAllDrawn = new LinkedList();
void applySurfaceChangesTransaction() {
......
// 1. 新的执行,清除数据
mTmpUpdateAllDrawn.clear();
......
// 2. 遍历所有窗口执行 lambda表达式
forAllWindows(mApplySurfaceChangesTransaction, true /* traverseTopToBottom */);
......
// Surface 操作
prepareSurfaces();
......
// 如果有需要更新的ActivityRecord则处理
while (!mTmpUpdateAllDrawn.isEmpty()) {
final ActivityRecord activity = mTmpUpdateAllDrawn.removeLast();
// See if any windows have been drawn, so they (and others associated with them)
// can now be shown.
// 3. 内部会设置这个ActivityRecord 下的 allDrawn 为true
activity.updateAllDrawn();
}
}
这里和 allDrawn 相关的操作分三步:
-
- 把 mTmpUpdateAllDrawn 的数据清除
-
- 在执行 mApplySurfaceChangesTransaction 这个 Lambda 的时候把符合条件的 ActivityRecord 添加到集合中
-
- 对集合进行遍历,内部将 allDrawn 设置为 true
先看第二步怎么把数据添加到 mTmpUpdateAllDrawn 的
2.5.1.1 mTmpUpdateAllDrawn 下元素的添加
这部分的代码之前也看过,当前关注下 allDrawn 相关的代码
java
# DisplayContent
private final Consumer<WindowState> mApplySurfaceChangesTransaction = w -> {
......
final WindowStateAnimator winAnimator = w.mWinAnimator;
......
// 判断当前是否有Surface
if (w.mHasSurface) {
// Take care of the window being ready to display.
// 重点 * 1. 主流程设置为 READY_TO_SHOW
final boolean committed = winAnimator.commitFinishDrawingLocked();
......
}
......
// 重点 * 2. allDrawn 相关逻辑
// 拿到这个WindowState所属的ActivityRecord
final ActivityRecord activity = w.mActivityRecord;
// 已经请求可见
if (activity != null && activity.isVisibleRequested()) {
activity.updateLetterboxSurface(w);
// 更新绘制状态
final boolean updateAllDrawn = activity.updateDrawnWindowStates(w);
if (updateAllDrawn && !mTmpUpdateAllDrawn.contains(activity)) {
// 符合条件加入集合
mTmpUpdateAllDrawn.add(activity);
}
}
w.updateResizingWindowIfNeeded();
};
commitFinishDrawingLocked 方法的时候将窗口状态设置成了 READY_TO_SHOW ,下面如果 ActivityRecord::updateDrawnWindowStates 返回 true 就可以添加到 mTmpUpdateAllDrawn 中了
ini
# ActivityRecord
boolean updateDrawnWindowStates(WindowState w) {
......
// 定义返回值
boolean isInterestingAndDrawn = false;
......
if (w != mStartingWindow) {
if (w.isInteresting()) {
......
if (w.isDrawn()) {
......
// 设置为true
isInterestingAndDrawn = true;
}
}
} ......
......
return isInterestingAndDrawn;
}
应用窗口这个方法走的是这个逻辑,返回值受 WindowState::isDrawn 影响,如果返回 true,则这整个方法也返回 true 了。
csharp
# WindowState
/**
* Returns true if the window has a surface that it has drawn a complete UI in to. Note that
* this is different from {@link #hasDrawn()} in that it also returns true if the window is
* READY_TO_SHOW, but was not yet promoted to HAS_DRAWN.
*/
public boolean isDrawn() {
return mHasSurface && !mDestroying &&
(mWinAnimator.mDrawState == READY_TO_SHOW || mWinAnimator.mDrawState == HAS_DRAWN);
}
根据注释和代码,前面的2个条件一般都满足,所以只要窗口状态为 READY_TO_SHOW 或者 HAS_DRAWN 就返回 true 。 然后还提到了,有一个 hasDrawn ,区别就是 hasDrawn 只要状态为 HAS_DRAWN 才返回true 。
csharp
# WindowState
public boolean hasDrawn() {
return mWinAnimator.mDrawState == WindowStateAnimator.HAS_DRAWN;
}
当前执行到这的时候窗口状态刚被设置为 READY_TO_SHOW ,所以满足条件返回 true 。
这块逻辑就清楚了。在 layout 的时候,会让每个窗口执行 mApplySurfaceChangesTransaction ,条件满足的话会把窗口状态设置成 READY_TO_SHOW ,并将这个窗口对应的 ActivityRecord 添加到 mTmpUpdateAllDrawn 集合。 然后再遍历集合执行 ActivityRecord::updateAllDrawn 。
2.5.1.2 设置 allDrawn = true
在 DisplayContent::applySurfaceChangesTransaction 方法最后会遍历 mTmpUpdateAllDrawn 的数据来执行 ActivityRecord::updateAllDrawn 方法
scss
# ActivityRecord
void updateAllDrawn() {
if (!allDrawn) {
......
if (numInteresting > 0 && allDrawnStatesConsidered()
&& mNumDrawnWindows >= numInteresting && !isRelaunching()) {
if (DEBUG_VISIBILITY) Slog.v(TAG, "allDrawn: " + this
+ " interesting=" + numInteresting + " drawn=" + mNumDrawnWindows);
// 重点* 1. 设置为true
allDrawn = true;
// Force an additional layout pass where
// WindowStateAnimator#commitFinishDrawingLocked() will call performShowLocked().
if (mDisplayContent != null) {
// 重点* 2. 还需要layout
mDisplayContent.setLayoutNeeded();
}
// 发生信息通知 Task 设置了 allDrawn
mWmService.mH.obtainMessage(H.NOTIFY_ACTIVITY_DRAWN, this).sendToTarget();
}
}
}
-
- 设置 allDrawn = true
-
- 设置标志位,那说明还需要再执行 layout (再一次执行 requestTraversal)
当前分析的是第一次执行 layout 时在执行 WindowStateAnimator::commitFinishDrawingLocked 方法的时候会把 WindowState 状态设置 READY_TO_SHOW ,但是由于此时 allDrawn = false,所以 ActivityRecord::canShowWindows 返回 false ,流程就不会往下走了。 不过这一次 layout 执行到 ActivityRecord::updateAllDrawn 把 allDrawn 设置为 true 了,并又触发了一次 layout 第二次 layout 还是会执行到 这个时候再执行到 WindowStateAnimator::commitFinishDrawingLocked 方法,这一次因为条件已经满足了,所以会执行 WindowState::performShowLocked 来把窗口状态设置成 HAS_DRAW
2.5.2 真正设置 -- HAS_DRAW
经过上面的分析,现在是第二遍执行 layout 走到 WindowStateAnimator::commitFinishDrawingLocked 方法时,条件满足执行到 WindowState::performShowLocked
kotlin
# WindowState
boolean performShowLocked() {
......
// 获取到当前状态
final int drawState = mWinAnimator.mDrawState;
// 当前分析过来的条件肯定都是满足的
if ((drawState == HAS_DRAWN || drawState == READY_TO_SHOW) && mActivityRecord != null) {
//窗口类型不为启动窗口
if (mAttrs.type != TYPE_APPLICATION_STARTING) {
// remonve startWindow 流程
mActivityRecord.onFirstWindowDrawn(this);
} else {
mActivityRecord.onStartingWindowDrawn();
}
}
// 不满足条件则直接返回
if (mWinAnimator.mDrawState != READY_TO_SHOW || !isReadyForDisplay()) {
return false;
}
......
// Force the show in the next prepareSurfaceLocked() call.
mWinAnimator.mLastAlpha = -1;
// 日志
ProtoLog.v(WM_DEBUG_ANIM, "performShowLocked: mDrawState=HAS_DRAWN in %s", this);
// 重点* 状态为HAS_DRAWN
mWinAnimator.mDrawState = HAS_DRAWN;
mWmService.scheduleAnimationLocked();
......
return true;
}
这里会对当前床头状态做检查,只有满足条件才会将状态设为 HAS_DRAWN。 然后因为这会窗口已经要显示了,可以移除 StartWindow 了。(以后会单独分析)
到这里窗口的状态已经设置成 HAS_DRAW 表示窗口已经显示了,但是这里只是状态,一个类的变量而已,还没看到实际显示的代码, 因为具体的 Surface 显示逻辑在 prepareSurfaces 中。
2.6 显示Surface
到这里状态状态已经是 READY_TO_SHOW 了,现在需要真正的将窗口显示事务提交到 SurfaceFlinger
java
# DisplayContent
@Override
void prepareSurfaces() {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "prepareSurfaces");
try {
// 1. 拿到事务
final Transaction transaction = getPendingTransaction();
// 2. 调用父类方法
super.prepareSurfaces();
// 3. 把事务merge到全局事务,供后续统一处理
SurfaceControl.mergeToGlobalTransaction(transaction);
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
这里的主要流程是"super.prepareSurfaces();",不过可以看到前后先是获取到了一个事务,然后再把这个事务merge到全局事务,这个全局事务就是 SurfaceControl 下面的一个类,也是一个 Surface 事务。
那说明这直接的 super.prepareSurfaces() 会有Surface 事务的处理,才需要把它 merge 到全局事务中。
当前逻辑中 super.prepareSurfaces() 内部对Surface 的处理就是将目标创建的 Surface 显示的事务
统一处理 GlobalTransaction 的时机就是在 2.2 小节看 RootWindowContainer::performSurfacePlacementNoTrace 方法的时候在执行 applySurfaceChangesTransaction 完成后处理。
DisplayContent 的父类是 DisplayArea ,不过 DisplayArea::prepareSurfaces 方法也是调用了父类 WindowContainer 的方法,所以直接看 WindowContainer::prepareSurfaces
csharp
# WindowContainer
void prepareSurfaces() {
......
for (int i = 0; i < mChildren.size(); i++) {
// 遍历孩子
mChildren.get(i).prepareSurfaces();
}
}
WindowContainer::prepareSurfaces 这个方法被很多子类重写,比如前面提到的 DisplayContent 和 DisplayArea ,另外还有像场景的 Task , ActivityRecord , WindowState 等,但是他们内部重写的逻辑也会再调用 "super.prepareSurfaces();"来遍历他的孩子,最终会调用到 DisplayContent 每个容器类,这里的调用略微有点绕,不过也不是很复杂,放慢思路理一下就好了。
其他类的重新不管,当前分析的窗口,所以直接看 WindowState 的实现
scss
# WindowState
@Override
void prepareSurfaces() {
......
// 主流程
mWinAnimator.prepareSurfaceLocked(getSyncTransaction());
// 调用父类,继续遍历它的孩子
super.prepareSurfaces();
}
主要是调用了 WindowStateAnimator::prepareSurfaceLocked 方法,注意参数是是获取了一个 Transaction ,说明要开始对 Surface 做操作了。
typescript
# WindowStateAnimator
void prepareSurfaceLocked(SurfaceControl.Transaction t) {
......
// 状态是 HAS_DRAWN 才执行
if (prepared && mDrawState == HAS_DRAWN) {
if (mLastHidden) {
if (showSurfaceRobustlyLocked(t)) {
......
}
}
}
......
}
private boolean showSurfaceRobustlyLocked(SurfaceControl.Transaction t) {
// 主流程
boolean shown = mSurfaceController.showRobustly(t);
if (!shown)
return false;
t.merge(mPostDrawTransaction);
return true;
}
这里又将 Transaction 交到了 WindowSurfaceController 处理,这个类我们在 relayoutWindow 的时候创建窗口 Surface 的时候提过,Window 的 Surface 创建是在这里类控制的,那么提交的逻辑交给他也很合理。
typescript
# WindowSurfaceController
boolean showRobustly(SurfaceControl.Transaction t) {
// 关键日志
ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE SHOW (performLayout): %s", title);
......
// 2. 内部将mSurfaceShown设置为true
setShown(true);
// 3. 重点* 真正的提交
t.show(mSurfaceControl);
......
}
-
- 这个日志很关键,表示 Framework 已经将 Surface 提交到 SurfaceFlinger 了。(严格来说需要等后面事务的apply)
-
- 将 mSurfaceShown 变量设置为true, 这个也是分析黑屏问题dump要看第一个关键变量,如果为 false 说明窗口并没有显示,可能是被遮挡了
-
- 这里看到操作 Surface Transaction 的地方了, 这个show ,就说明需要把 Suface 显示。 也是 finishDrawingWindow 最终的结果。
然后只要找到 Surface Transaction 做 apply 的操作的地方,就说明 Framework 层已经通知 SurfaceFlinger 了。
2.6.1 真正的窗口显示--提交显示Surface事务到SurfaceFlinger
这一步在 2.2 小节提过了,所以直接看 WindowManagerService::closeSurfaceTransaction 方法
typescript
# WindowManagerService
/**
* Closes a surface transaction.
* @param where debug string indicating where the transaction originated
*/
void closeSurfaceTransaction(String where) {
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "closeSurfaceTransaction");
SurfaceControl.closeTransaction();
mWindowTracing.logState(where);
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
# SurfaceControl
// SurfaceControl下定义的全局事务
static GlobalTransactionWrapper sGlobalTransaction;
public static void closeTransaction() {
synchronized(SurfaceControl.class) {
......
sGlobalTransaction.applyGlobalTransaction(false);
}
}
private static class GlobalTransactionWrapper extends SurfaceControl.Transaction {
void applyGlobalTransaction(boolean sync) {
......
// 重点* apply
nativeApplyTransaction(mNativeObject, sync);
}
}