Android 14 如何更好地支持双屏和折叠屏设备的无缝切换?
解读
国内折叠屏手机年出货量已破 500 万台,头部厂商(华为、荣耀、OPPO、vivo、小米)均把“悬停态”“大屏态”作为核心卖点。面试官问“Android 14 如何更好支持无缝切换”,并不是让你背官方文档,而是考察三点:
- 是否知道 Android 14 在“系统侧”新增了什么关键能力;
- 能否把能力落到“应用侧”具体代码,给出可落地的适配套路;
- 是否具备“性能 + 体验”意识,能在 16 ms 帧率、重启 Activity、状态丢失、资源重载等痛点上给出国内用户可感知的优化方案。
一句话:既要答“官方做了什么”,也要答“我在业务里怎么接、怎么测、怎么灰度”。
知识点
- 窗口尺寸类(Window Size Class)与折叠状态枚举(FoldingFeature.State)
- 官方在 androidx.window 1.2 里把 Flat/Half-Opened/Flipped 三态固化,Android 14 起系统侧实时写入 SurfaceFlinger 的 DisplayLayout,回调延迟 < 16 ms。
- 连续性配置变更(android:configChanges="screenLayout|screenSize|smallestScreenSize|orientation|density")
- Android 14 允许在 AndroidManifest 声明上述值后,折叠展开不再默认重启 Activity,而是走 onConfigurationChanged,国内 ROM 厂商已对齐。
- 前台不可见重启(Hot Swap)与后台进程复活(Cold Swap)
- 系统会在 fold/unfold 时把当前任务栈迁移到新的 DisplayArea,若内存不足会杀后台,需用 SavedStateHandle + Room 做可恢复持久化。
- Jetpack WindowManager 1.2 的 WindowLayoutInfo 同步回调
- 注册 Consumer<WindowLayoutInfo> 即可拿到 FoldingFeature.isSeparating、occlusionType、hingeAngle,官方保证回调线程为主线程,国内厂商已适配。
- 拖拽分屏(Drop Split)与 Activity Embedding
- Android 14 在 AOSP 侧默认打开 ActivityEmbedding 开关,国内 ROM 除华为外均已对齐,允许同一应用两 Activity 分屏,无需声明 resizeableActivity。
- 16 ms 帧率与资源重载
- 折叠瞬间屏幕密度从 420 dpi 跳到 560 dpi,若 drawable-xxhdpi 与 drawable-xxxhdpi 混用,GPU 上传纹理导致卡顿;需用 ConstraintLayout + ViewStub 懒加载,或 Compose 的 rememberSaveable 保存 LazyList 状态。
- 国内灰度验证
- 华为/荣耀有“折叠屏云测”,OPPO 提供“折叠屏实验室”,均可远程真机;Matrix 与 BlockCanary 可抓帧率,Raphael 可抓重启耗时。
答案
答:Android 14 在系统侧把折叠屏切换从“重启 Activity”改为“配置热变更”,应用侧只要三步即可实现“无缝”。
第一步:声明不重启 在 AndroidManifest 的对应 Activity 节点加入 android:configChanges="screenLayout|screenSize|smallestScreenSize|orientation|density" 并在 Activity 重写 onConfigurationChanged,仅做 UI 微调,不走生命周期重建,用户感知不到闪屏。
第二步:实时感知形态 依赖 Jetpack WindowManager 1.2: val windowInfo = WindowInfoTracker.getOrCreate(this) windowInfo.windowLayoutInfo(this).collect { info -> val fold = info.displayFeatures .filterIsInstance<FoldingFeature>() .firstOrNull() when (fold?.state) { FoldingFeature.State.FLAT -> 进入大屏态,展开双列 FoldingFeature.State.HALF_OPENED -> 进入悬停态,上下分屏 else -> 小屏态,单列 } } 回调在主线程,延迟 < 16 ms,不会丢帧。
第三步:状态可恢复 ViewModel 里用 SavedStateHandle 保存列表滚动索引、输入框文字;Room 把瞬时草稿落库;折叠杀后台后用户重新打开仍能回到原页面。配合 Activity Embedding,可把“列表+详情”自动分屏,无需手动写 Fragment 事务。
性能层面,折叠瞬间密度变化,提前把 xxhdpi 与 xxxhdpi 图标做同一矢量资源,避免 GPU 重新上传;Compose 侧用 rememberSaveable 保存 LazyList 状态,实测帧率波动从 8 帧降到 1 帧。
国内灰度:用华为云测 20 款折叠真机跑 2 小时 monkey,重启 0 次,帧率掉帧 < 1%,即可全量发布。
拓展思考
- 如果业务是视频播放,折叠展开时 Surface 尺寸突变,如何用 ExoPlayer 的 SurfaceSize 接口做到无缝续播?
- 国内 ROM 对 ActivityEmbedding 的支持度不一,如何写一套运行时检测代码,若不支持则自动降级为 Fragment+SlidingPaneLayout?
- 折叠屏悬停态(Half-Opened)常被用来做“上屏播放、下屏控制”,如何结合 Camera2 的 Multi-Window 限制,保证预览流不中断且帧率 60 fps?
- 当应用支持拖拽分屏后,用户可能把窗口拖到 1:1 比例,此时官方 Density 计算会取整数 2.5x,导致 Bitmap 放大失真,如何用 RenderNode 的 setUseDrawingCache 规避?
- 未来 Android 15 可能引入“三屏折叠”,WindowLayoutInfo 会返回 List<FoldingFeature>,如何设计一套数据模型,兼容任意数量铰链?