当前位置:首页>安卓APP>安卓fw面试被刷了?剖析wms的 defer / continueWindowLayout

安卓fw面试被刷了?剖析wms的 defer / continueWindowLayout

  • 2026-10-11 05:25:31
安卓fw面试被刷了?剖析wms的 defer / continueWindowLayout

背景

近期有学员在fw面试wms相关业务时候,有问到关于deferWindowLayout和continueWindowLayout相关的作用,学员对这块理解相对比较浅导致面试没有通过,今天我们来深入剖析一下关于这块内容(基于aosp13版本)。

在 Android 系统源码中,deferWindowLayout 和 continueWindowLayout 是成对出现的窗口布局控制方法,定义在 ActivityTaskManagerService 中,底层委托给 WindowSurfacePlacer 的 deferLayout 和 continueLayout。这套机制用于在连续修改多个窗口状态时,把布局计算延后到修改全部完成之后,统一执行一次。

一、为什么需要这两个方法

一般面试能回答到这个就是不错的,能回答到说明理解还是到位,当然背书也不行哈,还是要结合代码真正理解,才可以不变应万变。

窗口状态的任何变化(添加、移除、大小变化、可见性变化)最终都要触发一次全局布局计算,即 performSurfacePlacement。这个操作会遍历整个窗口树,重新计算所有窗口的 frame 和可见性,再同步给 SurfaceFlinger,开销比较大。

deferWindowLayout / continueWindowLayout 解决的问题有两个:

  1. 性能:连续修改多个窗口状态时,如果每次修改都立即触发布局,会重复执行多次 performSurfacePlacement。通过 defer 把布局延后,等所有修改完成后统一执行一次。
  2. 一致性:一组相关的状态修改作为一个整体提交,中间状态不会单独触发一次布局被渲染出来。

二、实现原理

1. WindowSurfacePlacer 中的计数器

核心实现位于 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 直接什么都不做(即取消了这次布局)。

2. performSurfacePlacement 的拦截

performSurfacePlacement 本身也会检查挂起状态:

// WindowSurfacePlacer.java:118finalvoidperformSurfacePlacement(boolean force){if (mDeferDepth > 0 && !force) {        mDeferredRequests++;return;    }// ... 真正的遍历计算 ...}

挂起期间(mDeferDepth > 0)且不是强制布局时,不执行布局,只把 mDeferredRequests 加 1。force 为 true 的调用可以绕过挂起状态直接执行。

3. ActivityTaskManagerService 中的封装

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);

三、典型使用场景

1. Activity 启动

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();

2. 屏幕旋转

DisplayRotation 在旋转后发送新配置、应用窗口事务时用 defer/continue 包住,保证配置更新和事务应用作为一个整体:

// DisplayRotation.java:603mService.mAtmService.deferWindowLayout();try {    mDisplayContent.sendNewConfiguration();if (t != null) {        mService.mAtmService.mWindowOrganizerController.applyTransaction(t);    }} finally {    mService.mAtmService.continueWindowLayout();}

3. Activity 结束(finishIfPossible)

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群,积极讨论各种行业难点痛点疑难问题,答疑服务等。

请联系马哥微信:

Android Framework开发rom实战合集课表/车载车机手机高级系统开发工程必会技能
重大消息:Hal+perfetto-systrace+SurfaceFlinger合集新专题发布
重大消息:ShellTransition项目实战专题aosp15版本首发优惠获取
开学第一课:安卓音频框架Audio子系统实战专题--首发优惠活动
2026第一课:安卓14-1车机手机三分屏实战专题--首发优惠活动

最新文章

随机文章