Android Framework(六)WMS-窗口显示流程——窗口内容绘制与显示

ops/2024/9/19 21:11:32/ 标签: android

文章目录

  • 窗口显示流程
    • 明确目标
  • 窗户内容绘制与显示流程
    • 窗口Surface的5种状态
    • 完整流程图
  • 应用端处理
    • finishDrawingWindow 的触发
  • system_service处理
    • WindowState状态 -- COMMIT_DRAW_PENDING
    • 本次layout 流程简述
    • applySurfaceChangesTransaction 方法概览
    • READY_TO_SHOW和HAS_DRAWN状态更改
    • SurfaceControl.Transaction::show
    • SurfaceControl.Transaction::apply--提交显示Surface事务到SurfaceFlinger
  • finishDrawingWindow流程总结

窗口显示流程

在这里插入图片描述
目前窗口的显示到了最后一步。
在 addWindow 流程中,创建挂载了 WindowState
在 relayoutWindow 流程为这个窗口创建了 Surface 并且还计算好了这个窗口的大小和在屏幕上的位置,并把窗口的 Surface 状态设置为了 DRAW_PENDING 。
这一步执行完后,应用端就可以开始绘制 View 树了,绘制完成后,需要把内容显示到屏幕上,也就是现在这个 Surface ,这一步就是本篇分析的内容:finishDrawingWindow 流程。

明确目标

现在可以明确 finishDrawingWindow 流程目的只有1个:把窗口的内容显示到屏幕上。
这里可能会有几个疑问:

  • 1、在 relayoutWindow 流程不是已经创建好 Surface 了吗?这一步目的是“把窗口的内容显示到屏幕上”,应用端拿到 Surface 绘制完 UI 等 VSync 来的时候上帧不就屏幕上有画面了?

  • 2、难道应用端每一帧绘制完都需要走这个 finishDrawingWindow 流程吗?

回答问题之前,先看一个案例:屏幕旋转

在屏幕旋转的时候会应用窗口的改变,这一阶段手机屏幕的 UI 的很混乱的,为了提示用户体验,google 的做法是旋转之前截个屏,然后创建一个层级很高的图层显示这个截图。这样一来旋转期间,用户看到的就是截图的内容。

这里又有个疑问,既然是这样那为啥还有那么多旋转黑屏的问题?这是因为动画执行的时间是写在动画文件里的,并不是根据旋转逻辑执行完毕来的(我本以为是以这个为动画结束条件)。当然这不是当前重点。

所以现在看一下这个截图图层的代码逻辑:

# ScreenRotationAnimation// 截屏图层SurfaceControlprivate 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();......// 获取截图buffGraphicBuffer buffer = GraphicBuffer.createFromHardwareBuffer(screenshotBuffer.getHardwareBuffer());......t.setBuffer(mScreenshotLayer, buffer);// 2. 设置截图buff给截图的图层t.setColorSpace(mScreenshotLayer, screenshotBuffer.getColorSpace());// 3. 执行显示 Surfacet.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的5种状态

窗口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::setViewViewRootImpl::requestLayoutViewRootImpl::scheduleTraversals             ViewRootImpl.TraversalRunnable::run              -- Vsync相关--scheduleTraversalsViewRootImpl::doTraversalViewRootImpl::performTraversals ViewRootImpl::relayoutWindowSession::relayout                -- 第二步:relayoutWindowViewRootImpl::updateBlastSurfaceIfNeededSurface::transferFrom        -- 应用端Surface赋值ViewRootImpl::performMeasure         -- View绘制三部曲 --MeasureViewRootImpl::performLayout          -- View绘制三部曲 --Layout  ViewRootImpl::createSyncIfNeededSurfaceSyncGroup::init ViewRootImpl::reportDrawFinished Session::finishDrawing   -- 第三步:finishDrawingWindowViewRootImpl::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 方法。

# ViewRootImplprivate 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@Overridepublic 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 方法。

# ViewRootImplprivate 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@Overridepublic 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::finishDrawingWindowWindowState::finishDrawingWindowStateAnimator::finishDrawingLocked    -- COMMIT_DRAW_PENDINGWindowPlacerLocked::requestTraversal           -- 触发layoutTraverser::runWindowSurfacePlacer::performSurfacePlacementWindowSurfacePlacer::performSurfacePlacementLoopRootWindowContainer::performSurfacePlacement  -- 开始layout逻辑RootWindowContainer::performSurfacePlacementNoTraceWindowManagerService::openSurfaceTransaction      -- 打开Surface事务RootWindowContainer::applySurfaceChangesTransaction  -- 处理Surface事务DisplayContent::applySurfaceChangesTransaction   -- 遍历每个屏幕DisplayContent::performLayout                          -- relayoutWinodw 流程DisplayContent::forAllWindows                          -- 每个窗口执行mApplySurfaceChangesTransactionWindowStateAnimator::commitFinishDrawingLocked     -- READY_TO_SHOWWindowState::performShowLocked                 -- HAS_DRAWNDisplayContent::prepareSurfaces                        -- Surface 处理WindowContainer::prepareSurfaces                   -- 遍历每个孩子WindowState::prepareSurfaces                   -- 忽略其他,只看窗口的实现WindowStateAnimator::prepareSurfaceLockedWindowSurfaceController::showRobustlyWindowSurfaceController::setShownSurfaceControl.Transaction::show    -- Surface显示WindowManagerService::closeSurfaceTransaction    -- 处理关闭Surface事务SurfaceControl::closeTransactionGlobalTransactionWrapper::applyGlobalTransactionGlobalTransactionWrapper::nativeApplyTransaction    -- 触发native

在这里插入图片描述

# WindowManagerServicefinal 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) {// 获取到对应的WindowStateWindowState win = windowForClientLocked(session, client, false);// T版本这里是个 proto日志Slog.w(TAG, "finishDrawingWindow: "+win+" mDrawState="+(win != null ? win.mWinAnimator.drawStateToString() : "null"));// 重点* 1. 执行WindowState::finishDrawingif (win != null && win.finishDrawing(postDrawTransaction, seqId)) {if (win.hasWallpaper()) {win.getDisplayContent().pendingLayoutChanges |=WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;}// 将当前WindowState.mLayoutNeeded置为truewin.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

# WindowStatefinal 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 ,这个是非常重要的一步。

# WindowStateAnimatorboolean 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还有专门的logProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Draw state now committed in %s", mWin);}mDrawState = COMMIT_DRAW_PENDING;// 表示需要 layoutlayoutNeeded = 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// 这个方法加上了tracevoid performSurfacePlacement() {Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "performSurfacePlacement");try {performSurfacePlacementNoTrace();} finally {Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);}}// 主要干活的还是这个void performSurfacePlacementNoTrace() {......// TraceTrace.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个类型的事务处理:

  • 1、applySurfaceChangesTransaction 方法,执行 Surface 事务

可以看到这行代码前后有 SurfaceTransaction 的打开和关闭,那说明这里的逻辑是会触发 Surface 操作的。

applySurfaceChangesTransaction 方法内部做很多事,比如上一篇的 layoutWindow 流程,当前分析的场景在这个方法里会执行将窗口状态设置为 READY_TO_SHOW 和 HAS_DRAWN ,并且构建一个 Surface 事务来显示当前窗口的 Surface 。

然后会在 WindowManagerService::closeSurfaceTransaction 方法中触发 SurfaceTransaction 的apply 把 Surface 操作的事务提交到 SurfaceFlinger 。

  • 2、checkAppTransitionReady 方法,执行 App 事务。
    这个方法也是很常见并且核心的,Framework 层专门定义了 AppTransition 来表示一些 APP 的事务,根据用户具体的操作执行对应的 App 事务。如果事务已经满足执行条件,则会触发对应的 AppTransition 执行,然后也有一些窗口动画的触发。
    checkAppTransitionReady 触发的流程也会触发如果条件满足,也是会触发设置窗口状态为 HAS_DRAWN 逻辑的。只不过随着 google 的代码更新,目前正常启动流程,HAS_DRAWN 的设置还是在 applySurfaceChangesTransaction 触发的流程里。这个后面还会再提一下。

这个方法知道一下即可,当前流程不重点分析。

applySurfaceChangesTransaction 方法概览

# RootWindowContainerprivate void applySurfaceChangesTransaction() {......// 遍历每个屏幕final int count = mChildren.size();for (int j = 0; j < count; ++j) {final DisplayContent dc = mChildren.get(j);dc.applySurfaceChangesTransaction();}......}

不考虑多个屏幕的场景。

# 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 为trueactivity.updateAllDrawn();}}

这里有有个3重要的流程:

  • 1、relayoutWinodw 流程计算窗口大小(已经分析过,当前不管)
  • 2、遍历每个窗口,执行 mApplySurfaceChangesTransaction 这个 lambda表达式 ,当前分析的场景是会把目标窗口状态设置为 READY_TO_SHOW 和 HAS_DRAWN
  • 3、Framework 层对Surface 的操作,当前场景就是会提交一个 Surface 显示的事务

第一点在 relayoutWindow 流程分析过来,本篇分析后面2个。

这里还有一个重要的数据结构:mTmpUpdateAllDrawn

这个集合存储的是这次 layout 执行到当前 applySurfaceChangesTransaction 方法时,哪些 ActivityRecord 需要更新 allDrawn 属性了。

ActivityRecord 下面的这个 allDrawn 变量表示当前 ActivityRecord 下面的窗口是否全部绘制。

执行方法前会先把 mTmpUpdateAllDrawn 清空,然后在方法末尾遍历是否有元素。 那这个集合的元素是在哪里添加的呢?

在每个 WindowState 执行 mApplySurfaceChangesTransaction 时,如果符合条件就会加入集合。

READY_TO_SHOW和HAS_DRAWN状态更改

状态的设置在上面说的3个重点流程的第二点,也就是这一句代码触发。

forAllWindows(mApplySurfaceChangesTransaction, true /* traverseTopToBottom */);

这句代码会从上到下遍历每个窗口,然后执行 lambda 表达式, mApplySurfaceChangesTransaction 的定义如下:

# DisplayContentprivate final Consumer<WindowState> mApplySurfaceChangesTransaction = w -> {......final WindowStateAnimator winAnimator = w.mWinAnimator;......// 判断当前是否有Surfaceif (w.mHasSurface) {// Take care of the window being ready to display.// 重点 * 1. 主流程设置为 READY_TO_SHOW 和 HAS_DRAWNfinal boolean committed = winAnimator.commitFinishDrawingLocked();......}......// 重点 * 2. allDrawn 相关逻辑// 拿到这个WindowState所属的ActivityRecordfinal 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个逻辑需要注意:

  • 1、主流程,将状态设置成 READY_TO_SHOW
  • 2、allDrawn 属性逻辑

当前还是先看设置 Surface 状态的流程,allDrawn相关的后面单独梳理。

mHasSurface 在 relayoutWindow 流程创建 Surface 时设置为 true ,表示当前 WindowState 的已经有 Surface 。
然后就调用其 WindowStateAnimator::commitFinishDrawingLocked 。

# WindowStateAnimatorboolean commitFinishDrawingLocked() {......// 1. 当前状态的判断,不满足则returnif (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_DRAWif (activity == null || activity.canShowWindows()|| mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {result = mWin.performShowLocked();}return result;......}

这里有4个点,其中后面2个很重要:

  • 1、这个方法每次 layout 的时候都可能会执行过来,所以要判断当前窗口的状态是否符合条件执行后面的逻辑
  • 2、这个日志是上层分析黑屏问题看状态状态的关键表示已经把窗口状态设置为 READY_TO_SHOW
  • 3、窗口状态设置成 READY_TO_SHOW ,这也是当前分析的主要里程碑
  • 4、满足3个条件之一就会执行下一步,将窗口状态设置为 HAS_DRAW
    • 4.1 不依赖 Activity 的窗口,一般是状态栏导航栏这种系统窗口,或者应用启动的悬浮窗
    • 4.2 ActivityRecord::canShowWindows 是否可以显示窗口。这个方法的返回值又受3个因素影响,下一小节解释。
    • 4.3 窗口类型为 StartWindow

可以看到现在窗口的状态已经是 READY_TO_SHOW 了,至于能不能执行到下一步的条件,当前场景是 Activity 下的窗口,所以如果要进去只能满足 “activity.canShowWindows()” 这条。
先给结论,绝大部分情况下,是满足条件的,所以先继续走主线任务,然后再看这个条件的具体控制条件,已经如果这里没满足条件,那又会在什么逻辑把 Surface 状态设置成 HAS_DRAW 。

继续看刷主线任务:HAS_DRAW 。

# WindowStateboolean 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();}}// 不满足条件则直接返回,避免重复设置HAS_DRAWNif (mWinAnimator.mDrawState != READY_TO_SHOW || !isReadyForDisplay()) {return false;}......// 日志ProtoLog.v(WM_DEBUG_ANIM, "performShowLocked: mDrawState=HAS_DRAWN in %s", this);// 重点* 状态为HAS_DRAWNmWinAnimator.mDrawState = HAS_DRAWN;mWmService.scheduleAnimationLocked();......return true;}

这里会对当前窗口状态做检查,只有满足条件才会将状态设为 HAS_DRAWN。 然后因为这会窗口已经要显示了,可以移除 StartWindow 了。(以后会单独分析).

第一条主线任务到这里已经完成了,状态状态已经被设置为 HAS_DRAWN 了!

但是这毕竟只是代码中状态,一个类的变量而已,还没看到实际显示 Surface 的代码。具体的 Surface 显示逻辑在 prepareSurfaces 中。

SurfaceControl.Transaction::show

到这里状态状态已经是 READY_TO_SHOW 了,现在需要真正的将窗口显示事务提交到 SurfaceFlinger。

# DisplayContent@Overridevoid 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 的时机就是在后面 WindowManagerService::closeSurfaceTransaction 方法处理。

DisplayContent 的父类是 DisplayArea ,不过 DisplayArea::prepareSurfaces 方法也是调用了父类 WindowContainer 的方法,所以直接看 WindowContainer::prepareSurfaces

# WindowContainervoid prepareSurfaces() {......for (int i = 0; i < mChildren.size(); i++) {// 遍历孩子mChildren.get(i).prepareSurfaces();}}

WindowContainer::prepareSurfaces 这个方法被很多子类重写,比如前面提到的 DisplayContent 和 DisplayArea ,另外还有像场景的 Task , ActivityRecord , WindowState 等,但是他们内部重写的逻辑也会再调用 “super.prepareSurfaces();”来遍历他的孩子,最终会调用到 DisplayContent 每个容器类,这里的调用略微有点绕,不过也不是很复杂,放慢思路理一下就好了。

其他类的重新不管,当前分析的窗口,所以直接看 WindowState 的实现。

# WindowState@Overridevoid prepareSurfaces() {......// 主流程mWinAnimator.prepareSurfaceLocked(getSyncTransaction());// 调用父类,继续遍历它的孩子super.prepareSurfaces();}

主要是调用了 WindowStateAnimator::prepareSurfaceLocked 方法,注意参数是是获取了一个 Transaction ,说明要开始对 Surface 做操作了。

# WindowStateAnimatorWindowSurfaceController mSurfaceController;void prepareSurfaceLocked(SurfaceControl.Transaction t) {......// 状态是 HAS_DRAWN 才执行if (prepared && mDrawState == HAS_DRAWN) {if (mLastHidden) {mSurfaceController.showRobustly(t);......}}......}

这个方法在 U 有修改,简化了流程。google的修改链接为:
谷歌修改

这里又将 Transaction 交到了 WindowSurfaceController 处理,这个类我们在 relayoutWindow 的时候创建窗口 Surface 的时候提过,Window 的 Surface 创建是在这里类控制的,那么提交的逻辑交给它也很合理。

# WindowSurfaceControllervoid showRobustly(SurfaceControl.Transaction t) {// 1. 关键日志ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE SHOW (performLayout): %s", title);......// 2. 内部将mSurfaceShown设置为truesetShown(true);// 3. 重点* Surface真正的显示t.show(mSurfaceControl);......}
  • 1、这个日志很关键,表示 Framework 已经将 Surface 提交到 SurfaceFlinger 了。(严格来说需要等后面事务的apply)
  • 2、将 mSurfaceShown 变量设置为true, 这个也是分析黑屏问题dump要看第一个关键变量,如果为 false 说明窗口并没有显示,可能是被遮挡了
  • 3、这里看到 SurfaceControl.Transaction::show 的调用地方了, 这个 show 就说明需要把 Suface 显示, 也是 finishDrawingWindow 最终的结果。

又完成了一个主线任务 SurfaceControl.Transaction::show
现在只要找到 SurfaceControl.Transaction::apply 的执行地方,就说明 Framework 层已经通知 SurfaceFlinger 了,整个流程也就结束。

SurfaceControl.Transaction::apply–提交显示Surface事务到SurfaceFlinger

根据前面的分析,直接看 WindowManagerService::closeSurfaceTransaction 方法

# 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) {......//  重点* applynativeApplyTransaction(mNativeObject, sync);}}

虽然没有看到 SurfaceControl.Transaction::apply 但是这个 GlobalTransactionWrapper 也是其子类,这里调用 native 方法也就是 apply 了。

finishDrawingWindow流程总结

当前流程在 layout 逻辑的处理流程图大概如下:
在这里插入图片描述
这里处理完后在 WindowManagerService::closeSurfaceTransaction 方法把这一次 layout 中对各个窗口的 Surface 操作统一提交到 Surface 处理,这样做有一个好处就是如果逐个提交不仅仅浪费资源,而且还能存在不同步的情况导致显示异常,把这一次 layout 所有的 Surface 提交统一放在一个 Transaction 里做提交,这样能确保显示的正常。
这样一来 finishDrawingWindow 流程的两条主线:状态,SurfaceTransaction 的 apply 就都执行完了。


http://www.ppmy.cn/ops/113126.html

相关文章

【ARM】SOC的多核启动流程详解

基础概念 • cold boot 冷启动&#xff0c;一上电就开始运行 • warm boot 热启动&#xff0c;只是复位一下 • Primary boot 只给主核跑的那段代码 • Secondary boot 给从核跑的代码 还两种配置&#xff1a; • reset地址是可编程的&#xff0c;则会配置PROGRAMMABLE_RESET_…

Git项目管理工具

分布式版本控制系统 实际操作: 设置用户信息 git config --global user.name “itcast” git config --global user.email "hello@itcast.cn" </

TCP Analysis Flags 之 TCP ZeroWindow

前言 默认情况下&#xff0c;Wireshark 的 TCP 解析器会跟踪每个 TCP 会话的状态&#xff0c;并在检测到问题或潜在问题时提供额外的信息。在第一次打开捕获文件时&#xff0c;会对每个 TCP 数据包进行一次分析&#xff0c;数据包按照它们在数据包列表中出现的顺序进行处理。可…

MacBook苹果电脑安装JDK8、JDK17教程,环境变量配置 + 快速切换JDK版本

MacBook苹果电脑安装JDK8、JDK17教程&#xff0c;环境变量配置 快速切换JDK版本 MacBook M1 安装JDK及环境变量配置 1、在Oracle官网下载JDK 2、安装JDk 3、配置环境变量 4、快速切换 1.下载JDK&#xff08;官网&#xff09; 1.1官网下载dmg安装包 Java Archive | Oracle J…

GPT代码记录

#include <iostream>// 基类模板 template<typename T> class Base { public:void func() {std::cout << "Base function" << std::endl;} };// 特化的子类 template<typename T> class Derived : public Base<T> { public:void…

大模型分离架构学习记录

1、大模型相关名词 TOE&#xff08;TCP Offload Engine&#xff09;是指TCP卸载引擎。它是一种网络技术&#xff0c;通过将TCP/IP协议栈的一部分处理任务从主机的CPU卸载到网卡&#xff1b; 也就是RDMANVLink :在单台服务器内 8 块 GPU 卡通过 NVLink 连接。不同服务器之间的 …

MySQL——数据库的高级操作(一)数据备份与还原(1)数据的备份

在操作数据库时&#xff0c;难免会发生一些意外造成数据丢失。例如&#xff0c;突然停电、管理员的操作失误都可能导致数据的丢失。为了确保数据的安全&#xff0c;需要定期对数据库进行备份&#xff0c;这样&#xff0c;当遇到数据库中数据丢失或者出错的情况&#xff0c;就可…

vue3+ant design vue 中弹窗自定义按钮设置及以冒号为基准布局

1、自定义弹窗按钮&#xff0c;去除取消和确定按钮。&#xff08;网上很多方法都是说通过插槽来实现&#xff0c;但是试了下不生效&#xff0c;那既然插槽不生效的话&#xff0c;干脆直接写按钮就好了&#xff09; <a-modalv-model:open"open"title"人员信息…

探索Go语言中的Goroutine并发机制

什么是Goroutine 在Go语言中,Goroutine 是程序中最基本的并发单位。事实上,每个Go程序都会自动创建一个goroutine,那就是主goroutine,程序启动时会立即执行。Goroutine是Go语言中处理并发问题的核心工具,因此理解它的工作原理至关重要。 简而言之,Goroutine是并发执行的…

ubuntu20.04编译mesa

依赖 # drm git clone https://gitlab.freedesktop.org/mesa/drm.git meson builddir/ ninja -C builddir/ install# wayland git clone https://gitlab.freedesktop.org/wayland/wayland.git meson setup builddir -Ddocumentationfalse ninja -C builddir/ install编译 sud…

qt绘制时钟

代码 #include "widget.h" #include "ui_widget.h"#include <QWidget> #include <QPaintEvent> //绘图事件 #include <QDebug> //测试 #include <QPainter> //画家 #include <QPen> //笔 #include <QBrush> //画刷 …

Vue路由:Vue router

目录 路由的基本概念 1. 路由 2. 单页应用SPA 3.前端路由的实现方式 3.1Hash模式 3.2History模式 Vue router 4 1.概述 2.安装使用 3.基础用法 3.1路由匹配规则声明 3.2动态路由匹配 3.3路由命名 3.4路由重定向 3.5路由嵌套 3.6命名视图 3.6声明式导航&编程…

python-奖金/贪心的小明

一&#xff1a;奖金 题目描述 企业发放的奖金根据利润提成。利润低于或等于 100000 元的&#xff0c;奖金可提 10%&#xff1b; 利润高于 100000 元&#xff0c;低于 200000 元&#xff08; 100000<I≤200000&#xff09;时&#xff0c;低于 100000 元的部分按 10% 提成&…

adb install失败: INSTALL_PARSE_FAILED_NO_CERTIFICATES

这个错误表明在尝试安装 APK 文件时出现了问题&#xff0c;原因是 APK 文件中的 AndroidManifest.xml 没有签名证书。在 Android 系统中&#xff0c;所有的应用都必须经过签名才能安装到设备上。以下是解决此问题的方法&#xff1a; 方法一&#xff1a;使用 Android Studio 或命…

一个好的云渲染,在动画渲染需要具备哪些条件

在当今快节奏的影视动画行业中&#xff0c;云渲染技术已经成为设计师和公司不可或缺的工具。它不仅减少了对昂贵硬件的依赖&#xff0c;还显著提高了工作效率&#xff0c;缩短了项目周期。云渲染提供高效、稳定、灵活且成本效益高的渲染服务&#xff0c;关键在于以下几个方面&a…

『功能项目』切换职业面板【48】

我们打开上一篇47技能冷却蒙版的项目&#xff0c; 本章要做的事情是切换职业UI面板的功能 首先双击打开Canvas预制体在左上主角面板信息中新建一个button按钮 重命名&#xff08;父物体是按钮Button&#xff0c;子物体Image即可&#xff09; 创建一个Image 设计一下布局 复制三…

【oj刷题】二分查找篇:二分查找算法的原理和应用场景

前言&#xff1a; 二分查找算法&#xff0c;又称折半查找算法&#xff0c;是一种在有序数组中查找特定元素的高效查找方法。它通过将搜索区间不断缩小一半&#xff0c;从而在对数时间内找到目标元素。二分查找是基于分治策略的一种典型应用&#xff0c;能够高效的处理许多问题&…

Unity实战案例全解析:PVZ 植物卡片状态分析

Siki学院2023的PVZ免费了&#xff0c;学一下也坏 卡片状态 卡片可以有三种状态&#xff1a; 1.阳光足够&#xff0c;&#xff08;且cd好了可以种植&#xff09; 2.阳光不够&#xff0c;&#xff08;cd&#xff1f;好了&#xff1a;没好 &#xff08;三目运算符&#xff09;&…

黑马java学习笔记11(阶段二 第三章3-1~3-2)

以下学习笔记记录于&#xff1a;2024.09.11-2024.09.17 文章目录 阶段二 JavaSE进阶第三章 JDK8新特性3-1 JDK8新特性1&#xff09;Lambda表达式66 认识Lambda表达式67 Lambda表达式的省略规则 2&#xff09;方法引用68 静态方法的引用、实例方法的引用69 特定类型方法的引用70…

门控循环单元(GRU)

困死了。。。 参考视频&#xff1a;56 门控循环单元&#xff08;GRU&#xff09;【动手学深度学习v2】 GRU&#xff1a;门控循环单元&#xff0c;与LSTM类似&#xff0c;解决RNN中不能长期记忆和反向传播中的梯度等问题。但结构比LSTM简单。 关注一个序列&#xff0c;不是每个观…