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

文章目录

  • 窗口显示流程
    • 明确目标
  • 窗户内容绘制与显示流程
    • 窗口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.mzph.cn/web/53085.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于springboot酒店管理系统

酒店管理系统 552酒店管理系统 摘 要 21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0c;由原来的感性认识向理性认识提高&#xff0c;管理工作的重要性已逐渐被人们所认识&#xff0c;…

通信工程学习:什么是GPON吉比特无源光网络

GPON&#xff1a;吉比特无源光网络 GPON&#xff08;Gigabit-Capable Passive Optical Network&#xff0c;吉比特无源光网络&#xff09;是一种基于ITU-T G.984.x标准的最新一代宽带无源光综合接入技术。该技术以其高带宽、高效率、大覆盖范围和用户接口丰富等特点&#xff0c…

Netty笔记01-Netty的基本概念与用法

文章目录 1. 概述1.1 Netty 是什么&#xff1f;1.2 Netty 的特点1.3 Netty 的作者1.4 Netty 的地位1.5 Netty 的优势1.6 Netty 的工作原理1.7 Netty 的应用场景1.8 Netty 的重要组件 2. 第一个程序2.1 目标2.2 服务器端2.3 客户端2.4 流程梳理&#x1f4a1; 提示 1. 概述 1.1 …

Give azure openai an encyclopedia of information

题意&#xff1a;给 Azure OpenAI 提供一部百科全书式的信息 问题背景&#xff1a; I am currently dabbling in the Azure OpenAI service. I want to take the default model and knowledge base and now add on to it my own unique information. So, for example, for mak…

Oracle按照某一字段值排序并显示,相同的显示序号

Oracle按照某一字段值排序并显示,相同的显示序号 最近的工作遇到对于相同的字段,按照序号去显示值,并对相同的值进行排序 实验了半天,感觉满意的答案,分享给大家 第一种: ROW_NUMBER 语法: ROW_NUMBER() OVER (ORDER BY your_column) AS sequence_number 说明: 根据your_column…

leetcode21. 合并两个有序链表

思路&#xff1a; 用一个新链表来表示合并后的有序链表&#xff0c; 每次比较两个链表&#xff0c;将较小的那个结点存储至新链表中 # Definition for singly-linked list. # class ListNode(object): # def __init__(self, val0, nextNone): # self.val val # …

[mysql]mysql排序和分页

#排序和分页本身是两块内容,因为都比较简单,我们就把它分到通一个内容里. #1排序: SELECT * FROM employees #我们会发现,我们没有做排序操作,但是最后出来的107条结果还是会按顺序发出,而且是每次都一样.这我们就有一个疑惑了,现在我们的数据库是根据什么来排序的,在我们没有进…

【与C++的邂逅】--- C++的IO流

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; 与C的邂逅 本篇博客我们来了解C中io流的相关知识。 &#x1f3e0; C语言输入输出 C语言中我们用到的最频繁的输入输出方式就是scanf ()与printf()。 sc…

论文解读《LaMP: When Large Language Models Meet Personalization》

引言&#xff1a;因为导师喊我围绕 “大语言模型的个性化、风格化生成” 展开研究&#xff0c;所以我就找相关论文&#xff0c;最后通过 ACL 官网找到这篇&#xff0c;感觉还不错&#xff0c;就开始解读吧&#xff01; “说是解读&#xff0c;其实大部分都是翻译哈哈哈&#x…

系统安全设计规范(Word完整版)

1.1 总体设计 1.1.1 设计原则 1.2 物理层安全 1.2.1 机房建设安全 1.2.2 电气安全特性 1.2.3 设备安全 1.2.4 介质安全措施 1.3 网络层安全 1.3.1 网络结构安全 1.3.2 划分子网络 1.3.3 异常流量管理 1.3.4 网络安全审计 1.3.5 网络访问控制 1.3.6 完整性检查 1.…

Cpp类和对象(上)(3)

文章目录 前言一、面向过程与面向对象初步认识二、类的引入三、类的定义四、类的访问限定符及类的封装类的访问限定符类的封装 五、类的作用域(类域)六、类的实例化七、类对象模型如何计算类对象的大小类对象的存储方式猜测 八、this指针this指针的引出this指针的特性 九、C语言…

1×1卷积核【super star 卷积核】

一、11卷积的作用 我们先来给出11卷积的一般作用&#xff0c;如下所示&#xff1a; • 跨通道的特征整合 • 特征通道的升维与降维 • 减少权重参数&#xff08;卷积核参数&#xff09; 【 简化模型 】 1.1 特征通道的升维与降维/跨通道的特征整合/简化模型 输入数据&…

Node.js 多版本安装与切换指南

一.使用nvm的方法 1. 卸载nodejs 如果你的电脑有安装nodejs&#xff0c;需要先卸载掉&#xff1b;若没有请直接下一步。 2. 前往官网下载nvm nvm&#xff1a;一个nodejs版本管理工具&#xff01; 官网地址&#xff1a;nvm文档手册 - nvm是一个nodejs版本管理工具 - nvm中文…

Oracle数据恢复—Oracle数据库误删除表数据如何恢复数据?

删除Oracle数据库数据一般有以下2种方式&#xff1a;delete、drop或truncate。下面针对这2种删除oracle数据库数据的方式探讨一下oracle数据库数据恢复方法&#xff08;不考虑全库备份和利用归档日志&#xff09;。 1、delete误删除的数据恢复方法。 利用oracle提供的闪回方法…

简单题21 - 合并两个有序链表(Java)20240917

问题描述&#xff1a; java代码&#xff1a; /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val val; }* ListNode(int val, ListNode next) { this.val val…

【DAY20240918】03教你轻松配置 Git 远程仓库并高效推送代码!

文章目录 前言 git diff一、远程仓库&#xff1f;1、在 Gitee 上新建仓库&#xff1a;2、Git 全局设置&#xff1a;3、添加远程仓库&#xff1a;4、推送本地代码到远程仓库&#xff1a;5、输入用户名和密码&#xff1a;6、后续推送&#xff1a; 二、全情回顾三、参考 前言 git …

二十种编程语言庆祝中秋节

二十种编程语言庆祝中秋节 文章目录 二十种编程语言庆祝中秋节中秋快乐&#xff01;家人们 &#x1f973;一 Python二 C三 C四 Java五 C#六 Perl七 Go八 Asp九 PHP十 JavaScript十一 JavaScript HTML十二 Visual Basic十三 早期 VB十四 Visual C十五 Delphi十六 Shell十七 Cobo…

场外个股期权通道商是什么业务?个人投资者可以参与场外期权吗?

今天带你了解场外个股期权通道商是什么业务&#xff1f;个人投资者可以参与场外期权吗?场外个股期权通道商在个股期权市场中起着重要的中介作用&#xff0c;提供定制化的交易服务和风险管理解决方案。 场外个股期权通道商 场外个股期权通道商是一种金融机构&#xff0c;主要…

15.10 在k8s部署grafana-deployment并导入k8s大盘

本节重点介绍 : grafana deployment部署k8s大盘导入 准备yaml 部署工作 1. 修改yaml中的节点选择器标签 k8s-node01改为你自己的节点 2. 在节点上创建数据目录 mkdir -pv /data/grafana3. 部署grafana # 部署 kubectl apply -f deployment.yaml # 检查 [rootprome-mast…

OpenCV高阶操作

在图像处理与计算机视觉领域&#xff0c;OpenCV&#xff08;Open Source Computer Vision Library&#xff09;无疑是最为强大且广泛使用的工具之一。从基础的图像读取、 1.图片的上下&#xff0c;采样 下采样&#xff08;Downsampling&#xff09; 下采样通常用于减小图像的…