deferWindowLayout和continueWindowLayout相关的作用,学员对这块理解相对比较浅导致面试没有通过,今天我们来深入剖析一下关于这块内容(基于aosp13版本)。在 Android 系统源码中,deferWindowLayout 和 continueWindowLayout 是成对出现的窗口布局控制方法,定义在 ActivityTaskManagerService 中,底层委托给 WindowSurfacePlacer 的 deferLayout 和 continueLayout。这套机制用于在连续修改多个窗口状态时,把布局计算延后到修改全部完成之后,统一执行一次。一般面试能回答到这个就是不错的,能回答到说明理解还是到位,当然背书也不行哈,还是要结合代码真正理解,才可以不变应万变。
窗口状态的任何变化(添加、移除、大小变化、可见性变化)最终都要触发一次全局布局计算,即 performSurfacePlacement。这个操作会遍历整个窗口树,重新计算所有窗口的 frame 和可见性,再同步给 SurfaceFlinger,开销比较大。
deferWindowLayout / continueWindowLayout 解决的问题有两个:
performSurfacePlacement。通过 defer 把布局延后,等所有修改完成后统一执行一次。核心实现位于 WindowSurfacePlacer。它用两个字段记录挂起状态:
// WindowSurfacePlacer.javaprivateint mDeferDepth = 0;/** The number of layout requests when deferring. */privateint mDeferredRequests;mDeferDepth:挂起深度计数器。deferLayout 每次加 1,continueLayout 每次减 1,减到 0 才恢复布局。用计数器而不是布尔值,是因为调用可能嵌套。mDeferredRequests:挂起期间被请求布局的次数。挂起期间如果有代码调用了 performSurfacePlacement,不会真正执行,而是把这次请求记到这个字段里。deferLayout 和 continueLayout 的实现:
// WindowSurfacePlacer.java:73voiddeferLayout(){ mDeferDepth++;}// WindowSurfacePlacer.java:86voidcontinueLayout(boolean hasChanges){ mDeferDepth--;if (mDeferDepth > 0) {return; }if (hasChanges || mDeferredRequests > 0) { performSurfacePlacement(); mDeferredRequests = 0; }}// WindowSurfacePlacer.java:104booleanisLayoutDeferred(){return mDeferDepth > 0;}continueLayout 只有在最外层(mDeferDepth 减到 0)时才真正执行布局,并且需要满足两个条件之一:
hasChanges 为 true:调用方明确说明挂起范围内有变化;mDeferredRequests > 0:挂起期间有代码请求过布局。两个条件都不满足时,这次 continue 直接什么都不做(即取消了这次布局)。
performSurfacePlacement 本身也会检查挂起状态:
// WindowSurfacePlacer.java:118finalvoidperformSurfacePlacement(boolean force){if (mDeferDepth > 0 && !force) { mDeferredRequests++;return; }// ... 真正的遍历计算 ...}挂起期间(mDeferDepth > 0)且不是强制布局时,不执行布局,只把 mDeferredRequests 加 1。force 为 true 的调用可以绕过挂起状态直接执行。
deferWindowLayout / continueWindowLayout 是对上面两个方法的封装,额外引入了一个 mLayoutReasons 位掩码,用来记录挂起范围内具体发生了什么变化:
// ActivityTaskManagerService.java:691staticfinalint LAYOUT_REASON_CONFIG_CHANGED = 0x1;staticfinalint LAYOUT_REASON_VISIBILITY_CHANGED = 0x2;/** The reasons to perform surface placement. */@LayoutReasonprivateint mLayoutReasons;// ActivityTaskManagerService.java:4398voiddeferWindowLayout(){if (!mWindowManager.mWindowPlacerLocked.isLayoutDeferred()) {// 只有第一次进入(最外层)才重置,因为只需要关心 defer 范围内的变化 mLayoutReasons = 0; } mWindowManager.mWindowPlacerLocked.deferLayout();}// ActivityTaskManagerService.java:4409voidcontinueWindowLayout(){ mWindowManager.mWindowPlacerLocked.continueLayout(mLayoutReasons != 0);}// ActivityTaskManagerService.java:4421voidaddWindowLayoutReasons(@LayoutReason int reasons){ mLayoutReasons |= reasons;}mLayoutReasons 的作用是让 continue 时能判断挂起范围内是否真的有变化。deferWindowLayout 在最外层入口把 mLayoutReasons 清零,挂起范围内如果有代码改动了配置或可见性,就调用 addWindowLayoutReasons 打上对应标记。最后 continueWindowLayout 把 mLayoutReasons != 0 作为 hasChanges 传给 continueLayout。这样只有在 defer 范围内确实发生了需要布局的变化时,continue 才会触发一次布局,避免无意义的空布局。
addWindowLayoutReasons 的几个调用点:
// ActivityRecord.java:5007 可见性变化时mAtmService.addWindowLayoutReasons( ActivityTaskManagerService.LAYOUT_REASON_VISIBILITY_CHANGED);// WindowOrganizerController.java:503 客户端没有自己处理配置时mService.addWindowLayoutReasons(LAYOUT_REASON_CONFIG_CHANGED);ActivityStarter 在启动 Activity 的核心逻辑 startActivityInner 前后加 defer/continue,把启动过程中的窗口变化合并成一次布局:
// ActivityStarter.java:1659mService.deferWindowLayout();Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "startActivityInner");result = startActivityInner(r, sourceRecord, voiceSession, voiceInteractor, startFlags, doResume, options, inTask, inTaskFragment, restrictedBgActivity, intentGrants);// ...mService.continueWindowLayout();
DisplayRotation 在旋转后发送新配置、应用窗口事务时用 defer/continue 包住,保证配置更新和事务应用作为一个整体:
// DisplayRotation.java:603mService.mAtmService.deferWindowLayout();try { mDisplayContent.sendNewConfiguration();if (t != null) { mService.mAtmService.mWindowOrganizerController.applyTransaction(t); }} finally { mService.mAtmService.continueWindowLayout();}ActivityRecord.finishIfPossible 是 Activity 结束的入口,defer/continue 包住了整个 finish 流程:
// ActivityRecord.java:3361mAtmService.deferWindowLayout();try { mTaskSupervisor.mNoHistoryActivities.remove(this); makeFinishingLocked();// ... finishActivityResults(resultCode, resultData, resultGrants);// ... mTransitionController.requestCloseTransitionIfNeeded(endTask ? task : this);if (isState(RESUMED)) {// 准备关闭转场动画 mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);// 提前截取 task 快照 mAtmService.mWindowManager.mTaskSnapshotController.snapshotTasks(tasks);// 通知 WM 这个窗口要移除 setVisibility(false);// 开始 pause 流程 getTaskFragment().startPausing(false/* userLeaving */, false/* uiSleeping */,null/* resuming */, "finish"); } elseif (!isState(PAUSING)) {// ...finalboolean removedActivity = completeFinishing("finishIfPossible") == null;// ...return removedActivity ? FINISH_RESULT_REMOVED : FINISH_RESULT_REQUESTED; }return FINISH_RESULT_REQUESTED;} finally { mAtmService.continueWindowLayout();}这个流程里会连续修改多个状态:把 activity 标记为 finishing、调整焦点、准备关闭转场、截取快照、把窗口设为不可见、启动 pause。这些变化如果分别触发布局,中间状态(比如窗口已经隐藏但转场还没准备好)就会被单独渲染。用 defer/continue 把它们合并成一次布局。
其余使用位置(RecentsAnimation.java:210、KeyguardController.java:245、Task.java、TaskFragment.java、WindowOrganizerController.java:387、RootWindowContainer.java:1980)都是同一个模式:deferWindowLayout(),中间改多个窗口状态,最后在 finally 里 continueWindowLayout() 保证即使抛异常也会恢复布局。
deferWindowLayout 和 continueWindowLayout 是 WMS 内部用于批量窗口操作时延后布局的机制。底层由 WindowSurfacePlacer 的 mDeferDepth 计数器实现嵌套安全的挂起/恢复,挂起期间的布局请求通过 mDeferredRequests 计数,最后统一执行一次。上层 ActivityTaskManagerService 额外用 mLayoutReasons 位掩码记录 defer 范围内发生的变化,使 continue 时能判断是否真的需要执行布局。这套机制用于 Activity 启动、屏幕旋转、Recents、Keyguard、窗口事务等需要一次性修改多个窗口状态的场景。
学习fw课程和性能相关知识有啥疑问,请记得联系马哥本人或者在马哥vip群中进行讨论
更多vip干货独享知识,及面试定制指导上岸fw工程师服务,课程优惠购买成为vip学员进入vip群,积极讨论各种行业难点痛点疑难问题,答疑服务等。
请联系马哥微信:
