Jetpack WindowManager 如何帮助开发者处理多窗口和折叠状态?
解读
国内折叠屏手机年出货量已破 500 万台,华为、荣耀、OPPO、vivo 均把“大屏适配”列为应用上架审核重点。面试官问这道题,不是想听“官方文档怎么说”,而是验证三件事:
- 你是否真的在国产折叠屏真机上踩过坑;
- 能否把“可折叠”与“多窗口”这两个维度拆成独立变量,给出可落地的窗口级适配策略;
- 是否理解 Google 方案与国产 ROM 差异,知道在哪层做兜底。
一句话:这道题考察的是“真机验证 + 窗口级思维 + 国产兼容”。
知识点
-
WindowManager 核心组件
- WindowBackend:官方唯一可信的折叠状态数据源,回调在主线程,可感知 FEATURE_SPLIT_BEHAVIOR、FEATURE_EXPANDED_WINDOW 等国产扩展。
- WindowLayoutInfo:包含 DisplayFeature 列表,区分 FoldingFeature 与 HingeAngleSensor 数据;FoldingFeature.isSeparating 为 true 时即“物理铰链遮挡”,需触发布局避让。
- WindowMetrics:替代 deprecated 的 Display.getSize(),给出当前窗口的像素尺寸与密度,支持多窗口下“一个 Activity 两块区域”的奇葩场景。
-
生命周期绑定方式
- 传统 onConfigurationChanged 只触发整屏旋转,不感知铰链;必须在 Activity/Fragment 的 LifecycleScope 里注册 WindowInfoTracker.callbackFlow,配合 repeatOnLifecycle(STARTED) 自动解注册,防止国产 ROM 熄屏后重绘导致的内存泄漏。
-
国产 ROM 差异与兜底
- 华为 EMUI:铰链区域返回 84 dp,但系统导航栏可能在折叠后自动迁移到左屏,导致 getWindowVisibleDisplayFrame 瞬移;需用 WindowMetrics#getBounds() 重新计算。
- 小米 HyperOS:自由窗口模式下,WindowBackend 回调延迟 120 ms,需加 Debounce;同时支持“平行视窗”,要求声明 meta-data miui.freeform.divider,否则分屏比例被强制锁 1:1。
- 荣耀 MagicOS:折叠展开后若应用未声明 android.resizeableActivity="true",系统会强制重启进程;需在 AndroidManifest 加 android.supports_size_changes="true" 并复写 androidx.window.util.ActivityEmbeddingController。
-
多窗口与折叠的交叉矩阵
- 折叠+分屏:铰链居中,左右各一个 Task,此时 FoldingFeature.bounds.left 恰好等于系统 divider 位置,可用作分割线,无需硬编码 50%。
- 展开+画中画:Activity 处于 PIP,WindowMetrics 返回的是 PIP 小窗尺寸,FoldingFeature 为 null;必须双重判断“isInPictureInPictureMode && foldingFeatures.isEmpty()”再走小窗布局。
- 桌面模式(三星 DeX、华为 PC 模式):外接显示器无 FoldingFeature,但 WindowMetrics 宽度可能大于 1600 dp,此时应切换平板布局而非折叠布局,防止“大屏用手机 UI”的审核拒绝。
-
性能与稳定性
- 回调频率:折叠动画 240 Hz,WindowLayoutInfo 最快 16 ms 一次,直接在主线程里 requestLayout 会掉帧;需使用 distinctUntilChanged() 过滤重复值,再切换到 Dispatchers.Main.immediate 做差分更新。
- 旋转锁:国产 ROM 在 180° 悬停时可能来回抖动,建议加 200 ms 防抖,且只在“STATE_FLAT”与“STATE_HALF_OPENED”之间切换,忽略中间态。
答案
“我在荣耀 Magic V2 与华为 Mate X3 上做了完整验证,总结为三步:
第一步,用 WindowInfoTracker.callbackFlow 拿到 WindowLayoutInfo,通过 repeatOnLifecycle 自动绑定,避免国产 ROM 熄屏重绘泄漏;
第二步,解析 FoldingFeature:若 isSeparating=true,表示铰链遮挡,此时用 FoldingFeature.bounds 作为分割线,把 RecyclerView 拆成双栏,左侧 Category、右侧 Detail,实现官方说的 List-Detail;若 isSeparating=false 但 orientation==VERTICAL,则属于悬停模式,把视频播放区固定到上半屏,下半屏放弹幕控件;
第三步,处理多窗口交叉场景:当应用处于分屏且 WindowMetrics.widthDp>=840 时,不再以折叠状态为准,而是直接走平板布局,防止大屏用手机 UI 被华为应用市场打回;同时针对小米自由窗口的 120 ms 延迟,用 debounce(200) 过滤,确保动画不抖动。
上线后通过 Android Vitals 观察到,折叠展开过程中的 overdraw 降低 18%,华为审核一次通过。”
拓展思考
-
如果业务要求“折叠展开不重启 Activity”,而国产 ROM 强制重启,如何绕过?
提示:在 Application 级通过 ActivityEmbeddingController 设置 splitPairRule,把同一 Activity 拆成双任务栈,系统会认为你已适配大屏,从而取消重启;但需自行同步 ViewModel 状态,可用 SavedStateHandle + Hilt 实现跨栈复用。 -
当铰链角度传感器与 FoldingFeature 冲突时信谁?
提示:传感器更新频率高但可能被厂商校准屏蔽;FoldingFeature 是系统综合决策,优先级最高;但游戏类场景需要 90° 悬停精确值,可注册 SensorManager 做二次校验,只在 angle in 85..95 且 FoldingFeature.state==HALF_OPENED 时触发掌机模式。 -
国内隐私合规要求“折叠态变化不得重置敏感输入”,如何落地?
提示:在 onSaveInstanceState 里把验证码、身份证号等敏感字段手动置空,防止系统重启后自动恢复;同时用 EncryptedSharedPreferences 把临时数据落盘到 TEE,折叠展开后通过 lifecycleScope 异步恢复,既满足合规又避免主线程阻塞。