资讯详情

资讯详情

Android 14-17自由窗口演进与适配实践全解析

做 Android 系统开发或者大屏应用适配的朋友最近两年应该都有同一个感觉自由窗口Freeform这个功能正在从“开发者选项里的隐藏玩具”慢慢变成“桌面化体验的核心底座”。我自己从 Android 14 一路跟到 16 的开发者预览再到周边生态传出的 Android 17 走向明显感觉到 Google 在窗口管理这条线上的思路越来越清晰。这篇文章我就把这段演进脉络完整梳理一遍重点讲清楚每个版本到底改了什么、为什么这么改以及作为开发者应该怎么跟着调整。无论你是做 ROM 定制、系统应用还是正在适配平板和折叠屏这篇都值得看完。1. 先把 Freeform 的底细摸清窗口模式三兄弟与演进主线1.1 多窗口三兄弟全屏、分屏与自由窗口要把 Freeform 讲清楚得先回到 Android 多窗口的基础框架。从系统窗口管理的角度看Android 一共定义了三种窗口模式全屏Fullscreen、分屏Split-screen、自由窗口Freeform。全屏不用多说就是传统手机应用默认状态分屏是 Android 7.0 引入的屏幕被切成上下或左右两块每块容纳一个应用自由窗口则是最接近桌面操作系统的形态应用窗口有独立标题栏可以随意拖动、缩放多个窗口可以叠放也可以互相遮挡。这三种模式在系统里的落点其实都围绕同一个核心概念——Task。每个 Task 代表用户正在做的一件“事情”里面可以有一个或多个 Activity。分屏模式下系统创建两个并排的 Task而自由窗口模式下每个窗口本质上也是一个独立的 Task只是这个 Task 带有自由尺寸的窗口边界和装饰标题栏、最小化按钮等。理解了这个关系后面再看各个版本的改动就清晰了Google 一直在做的事情其实就是让 Task 和窗口之间的关系更灵活、更可控。1.2 演进逻辑为什么 Android 14 到 17 这段路特别关键很多人以为自由窗口是最近才有的东西其实不是。早在 Android 7.0 时代AOSP 里就已经埋了 Freeform 的实现只是当时处于半隐藏状态——普通用户根本接触不到开发者需要在开发者选项里手动开启或者通过 adb 设置全局开关才能调出来。那个阶段的 Freeform 说实话体验很糟糕窗口拖动卡顿、键盘弹出错乱、应用动不动崩溃我自己当年在 Pixel C 上试过一次就放弃了纯粹是系统工程师用来验证窗口管理器能力的实验品。但从 Android 12L 开始情况有了变化。12L 是 Google 专门为大屏设备做的分支版本引入了任务栏Taskbar任务栏上可以固定应用、快速切换而且长按应用图标或拖拽就能触发分屏。这其实就是在为 Freeform 的普及做铺垫——用户有了一个自然的入口去触发多窗口而不需要进开发者选项翻开关。到了 Android 14、15大屏设备尤其是 Pixel Tablet、折叠屏的市场定位越来越明确Google 开始把自由窗口从“隐藏实验”推向“正式能力”只是推进的方式不是一步到位而是通过 API 行为变化、窗口策略收紧、任务栏强化这几个方向渐进式完成。Android 16 前后的桌面窗口模式信号已经很明显Android 17 的方向也就顺理成章。这段演进最值得关注的核心其实不是某个具体功能而是 Google 对“手机以外的 Android 长什么样”这件事的答案越来越确定。把这条主线看懂了版本细节就都串起来了。2. Android 14 的转折点大屏适配从“鼓励”变成了“硬约束”2.1 API 34 里影响自由窗口的几个关键变化Android 14API 34表面上没有喊出“自由窗口正式开放”这种口号但它在一堆基础行为上做了实质性收紧直接影响 Freeform 的可用性。最典型的是对 targetSdk 34 及以上应用的多窗口行为调整如果一个应用声明了不可调整尺寸android:resizeableActivityfalse当它运行在自由窗口或分屏模式下时系统不再简单地把它拉伸铺满屏幕而是用信箱模式Letterbox把它框在一个固定大小的区域内周围留黑边或放壁纸内容。这个改动的意义在于以前应用可以“赖”在手机上只支持竖屏全屏到了大一屏环境下系统会强制给这种应用套一个笼子保证窗口管理体系的一致性。对 Freeform 来说这意味着更多应用可以被放进自由窗口而不至于把整个窗口管理器搞崩。另外Android 14 还加强了窗口尺寸的传递机制系统在窗口模式切换后会更准确地回调onConfigurationChanged应用可以通过WindowMetrics拿到真实的窗口边界而不是沿用整块屏幕的尺寸。还有一个容易忽略的点Android 14 对大屏设备的内存和显示资源管理做了优化当 Freeform 窗口数量增多时系统会更激进地回收不可见窗口的资源。我在真机上同时开四个自由窗口切来切去明显感觉后台窗口被冻结的频次更高了但这其实是好事避免了窗口一多系统直接卡死。2.2 实测感受自由窗口稳定性与兼容性的质变Android 14 之前我在自己编译的 AOSP 版本上开 Freeform经常遇到两个问题一是窗口拖动时掉帧尤其是背景还有另一个应用在播放视频的时候二是某些应用进入自由窗口后布局直接错乱比如相机应用打开预览黑屏、视频播放器拉伸变形。Android 14 版本这部分改善非常明显。拖动窗口的帧率已经接近桌面系统的流畅度这得益于 WindowManager 在窗口位置更新、输入事件投射、同步绘制调度上的改进。布局错乱问题虽然还时有发生但比例大幅下降原因是系统在自由窗口模式下对那些未适配应用的默认处理更成熟了——要么走信箱模式要么给一个合理的默认尺寸而不是让应用自己在错误的测量下渲染。当然这并不意味着应用什么都不用做就能获得完美体验。Freeform 和全屏、分屏本质上是不同的窗口环境应用的资源配置尺寸断点、方向策略、亮色/暗色主题下的窗口装饰都会影响最终效果。Android 14 的意义在于它把这块地基重新夯实了一遍让上层适配有了稳定依赖。2.3 升级到 Android 14 后必须做的适配动作如果你正在做应用的大屏和自由窗口适配targetSdk 升到 34 之后有几件事需要做。首先是全面检查布局在不同窗口尺寸下的表现从这里开始 SystemUI 和应用都可以把手伸到 DisplayCutout 区域所以窗口内容需要正确应用 insets尤其是自由窗口模式下顶栏、底栏、IME 的位置都和全屏时不一样。其次如果应用之前通过android:resizeableActivityfalse锁死了横竖屏或只允许全屏到了 Android 14 建议重新评估在自由窗口日益普及的趋势下拒绝 resizeable 等于主动放弃桌面场景的用户。Google Play 对大屏设备上的应用质量也有了审核倾向不可调整尺寸的应用在未来很难拿到好展示位。最后建议趁升级版本的机会把WindowMetrics相关的获取逻辑统一替换掉。旧的getRealSize、getDisplayMetrics在自由窗口场景下拿到的通常是整个屏幕的尺寸而不是当前窗口的可用区域这会直接导致布局计算错误。用 Jetpack 的WindowMetricsCalculator配合currentWindowMetrics()才能拿到当前窗口真实可绘制的尺寸。3. Android 15 的关键补位窗口度量、任务栏与 IME 终局3.1 WindowMetrics 与可调尺寸窗口的关系Android 15API 35在自由窗口演进里承担的角色更像是把 Android 14 留下的适配问题系统化地解决掉。最核心的一个变化是窗口边界和窗口尺寸的语义被彻底收敛系统不再允许应用通过getRealMetrics这类旧 API 去“猜”自己的尺寸而是要求应用基于WindowMetrics来响应窗口变化。这个改动处理了一个长期让开发者头疼的问题——窗口度量在多窗口环境下的混乱。自由窗口模式下窗口尺寸可以随时被用户拖动改变如果应用在初始化时读取一次屏幕尺寸然后缓存起来窗口一变尺寸就全乱了。Android 15 配合 Jetpack WindowManager 1.4让WindowMetrics的回调成为应用感知窗口变化的主通道配合onConfigurationChanged可以非常精准地响应尺寸变化。我自己的体验是在 Android 15 上做自由窗口适配时只要统一用WindowMetrics获取尺寸再结合WindowWidthSizeClassCompact / Medium / Expanded做布局分级基本上兼容性不会出大问题。相比之下还在用旧度量 API 的应用在 Android 15 上很容易出现“窗口缩放后 UI 不刷新”的怪现象。3.2 任务栏把 Freeform 从“隐藏玩法”推向前台Android 15 在 SystemUI 上对任务栏做了大量打磨。任务栏从简单的应用固定区变成了一个半透明的、可以随时呼出的常驻导航模块。这个变化对 Freeform 的意义非常直接任务栏成为了用户触发自由窗口的核心入口。在大屏设备上用户可以通过三种方式进入自由窗口在任务栏上拖拽应用到屏幕中央松手、从最近任务列表中选择窗口化、或者使用键盘快捷键桌面模式下常用。任务栏还会显示当前处于自由窗口状态的应用图标方便用户快速切换。为了支撑这种交互Android 15 在 WindowManager 里加强了“任务捕获”和“窗口快照”机制自由窗口在最小化、恢复、切换时会有更平滑的过渡动画。从开发者视角这其实是一种隐性的强制适配信号当自由窗口成为主线交互路径的一部分应用就不能再假装它不存在。如果你的应用在任务栏拖拽进入自由窗口后会闪退或布局崩坏用户的第一反应不是怪系统而是给应用打低分。3.3 用 adb 在 Android 15 上调试 Freeform 的实际姿势就算系统已经支持普通第三方应用想主动验证自由窗口体验最直接的方式还是通过 adb。在 Android 15 上我习惯用这么一组命令来调试# 开启自由窗口全局支持部分设备可能无效需要看 ROM adb shell settings put global enable_freeform_support 1 # 强制应用以可调整尺寸模式运行 adb shell am start -n com.example.app/.MainActivity \ --windowingMode 5这里的--windowingMode 5就是自由窗口模式的编号--windowingMode 1是全屏、--windowingMode 2是分屏、--windowingMode 4是画中画。不过要注意Android 14 之后这条命令的可用性取决于系统权限很多 ROM 会拒绝普通应用直接指定窗口模式。这时候我的替代方案是去开发者选项里找“启用自由窗口”或“强制桌面模式”之类的开关或者直接上模拟器验证。个人经验是AOSP 模拟器的 Tablet 镜像配合enable_freeform_support1是目前最接近真实自由窗口环境的调试手段。3.4 IME 与 insets自由窗口下最容易翻车的环节Android 15 还做了一件影响深远的事强制应用适配 edge-to-edge针对 targetSdk 35。这本来是为了让应用充分利用全面屏但对 Freeform 来说直接影响是输入法IME的遮挡处理变得更重要了。自由窗口模式下IME 不再像全屏那样从屏幕底部顶起整个界面而是只在一个窗口范围内生效。如果应用没有正确处理 IME 的 insets键盘弹出来就会直接遮住输入框而且窗口本身不会自动调整大小。正确做法是通过WindowInsetsCompat.Type.ime()监听键盘高度然后给根布局动态加上对应的底部 paddingViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets - val imeInsets insets.getInsets(WindowInsetsCompat.Type.ime()) v.updatePadding(bottom imeInsets.bottom) insets }这个问题我见过太多应用踩坑了——全屏时键盘弹起正常一进自由窗口就完蛋。本质原因就是 insets 处理不够精细没有按窗口区域而非屏幕区域来响应 IME 变化。Android 15 强化 edge-to-edge 之后这个问题从“偶尔犯病”变成了“必现问题”适配优先级必须提到最高。4. Android 16 的桌面化野心与 Android 17 的方向推演4.1 Android 16 已知的动态桌面窗口模式走上前台到了 Android 16整个演进节奏明显提速。虽然 Google 官方对于“自由窗口”这个说法依然保持克制但大屏设备上的桌面窗口模式Desktop Window Mode已经被清晰定位成正式能力。从开发者预览阶段流出的信息看Android 16 在以下几个方面做了深入调整。首先是任务栏进入更完整的形态支持固定分组、跨应用拖拽、甚至窗口间的文件拖放。其次窗口生命周期管理从 Activity 层扩展到整个 Task 层系统可以独立地挂起、恢复某个自由窗口而不影响其他窗口。第三键盘鼠标的交互被纳入窗口管理器一级的支持比如通过鼠标拖拽窗口边缘调整大小、通过快捷键触发窗口排列左半屏、右半屏、最大化等。这些变化的本质是把 Freeform 从“手机上可以开多个窗口”的简单逻辑升级成“这是一个可以当主力桌面来用的窗口环境”。在 Android 16 的开发者预览中我尝试在 Pixel Tablet 上同时打开浏览器、笔记应用和文件管理器三个窗口可以自由叠放、调整大小、切换焦点整个交互模式跟桌面操作系统已经没有本质区别。虽然还远没有 ChromeOS 那么成熟但作为系统底座已经站得住了。4.2 Android 17 可能的演进窗口记忆、白名单与窗口协议Android 17 目前还没有官方正式发布但从 Android 16 的走向和系统代码里预留的痕迹可以合理推演出几个方向。第一是窗口记忆和布局恢复。未来的自由窗口应该能记住用户上次打开应用时的窗口位置、大小、甚至窗口层级关系下次打开时原样还原。这在桌面系统上是基础能力但在 Android 上牵涉到 Task 序列化、窗口快照恢复、以及多窗口状态下 Activity 重建策略不是个简单活。第二是窗口权限和白名单机制。自由窗口天然比全屏更复杂也存在被恶意应用滥用的风险比如伪装成系统窗口、遮挡其他应用、制造钓鱼界面。Android 15 和 16 对SYSTEM_ALERT_WINDOW这类窗口类型已经收紧了权限Android 17 大概率会把这种管控扩展应用到自由窗口的创建权上比如要求应用声明特定的窗口能力才能进入自由窗口模式。第三是窗口协议和跨设备扩展。Android 16 已经在为可折叠设备、外接显示器的多窗口做准备Android 17 有可能会把自由窗口的语义推广到更广的形态——比如手机连上显示器后自动切换成自由窗口环境或者手机和大屏之间无缝迁移自由窗口。这些能力在代码里已经能零散地看到雏形只是离完整落地还有距离。4.3 对应用开发和 ROM 生态的长期影响从 Android 14 到 17 的演进对开发者的影响是分层的。最表层是适配要求变了应用的窗口尺寸不再是可以假定的常量而是一个用户可随时改变的变量。再往下一层是架构要求变了应用的 UI 状态必须可以在 Activity 重建、尺寸变化、窗口模式切换之间完整保存和恢复ViewModelSavedStateHandle以及持久化方案不能是“可选优化项”而是标准门槛。对 ROM 厂商来说自由窗口的演进也给了二次创新的空间。国内厂商在大屏、折叠屏上的多窗口能力一直走得比原生快Android 16 的桌面窗口模式推出后厂商可以基于这套更稳定的底座去实现自己的平级多任务、窗口手势、悬浮球等差异化功能而不用像以前那样在 WindowManager 里打一堆补丁。这也是为什么我一直觉得跟进 AOSP 上游的窗口管理演进比维护自己的私有方案要省力得多。5. 把应用调教好Freeform 场景下的适配经验与踩坑记录5.1 清单配置resizeableActivity、configChanges 与最小尺寸先解决最基础的问题怎么让你的应用允许进入自由窗口。关键在AndroidManifest.xml里这几个配置application android:resizeableActivitytrue activity android:name.MainActivity android:resizeableActivitytrue android:configChangesscreenSize|smallestScreenSize|screenLayout|orientation|keyboardHidden android:windowSoftInputModeadjustResize / /applicationresizeableActivity是总开关不设或设为 true 代表允许系统把它放进分屏或自由窗口模式。configChanges这里要谨慎——如果你声明接管screenSize和smallestScreenSize意味着窗口尺寸变化时不会重建 Activity而是回调onConfigurationChanged。这个策略有利有弊好处是状态不丢失、效率高坏处是你要自己处理所有尺寸相关资源的更新。我的建议是对于 UI 结构简单的应用可以接管 configChanges对于有复杂分栏、列表详情联动、或者用到 Camera/MediaCodec 这类硬件的应用更推荐让系统重建 Activity走onSaveInstanceStateViewModel恢复状态逻辑上更安全。另外别忘了在 manifest 里给 Activity 设置最小尺寸防止用户把窗口缩到布局无法支撑的大小activity android:name.MainActivity android:minWidth360dp android:minHeight480dp /系统会尊重这个约束在窗口缩到最小尺寸后自动停止用户的缩放操作。5.2 状态保存窗口缩放与模式切换别丢状态自由窗口最容易遇到的问题不是“窗口开不出来”而是“窗口一变状态就丢了”。比如用户正在表单里填写内容拖动了一下窗口边缘Activity 重建填了一半的数据没了——这种体验放在桌面应用上是不可接受的放在 Android 自由窗口里同样是致命伤。要解决这个问题得把状态保存的完整链路建立起来。分三层第一层用onSaveInstanceState保存临时 UI 状态滚动位置、输入框内容、选中项第二层用ViewModel持有与配置无关的界面数据第三层如果用到了数据库或网络数据用SavedStateHandle配合持久化方案保证进程被回收后也能恢复。给一个SavedStateHandle的典型写法class EditorViewModel(private val state: SavedStateHandle) : ViewModel() { var draftTitle: String get() state.getString(KEY_TITLE) ?: set(value) state.set(KEY_TITLE, value) var selectedItemId: Long get() state.getLong(KEY_ITEM_ID) ?: -1L set(value) state.set(KEY_ITEM_ID, value) }配合 Compose 的rememberSaveable或 View 体系的onSaveInstanceState基本能覆盖自由窗口下百分之九十的状态问题。我在多个项目里验证过这个方案窗口怎么拖、模式怎么切状态都能保住。5.3 尺寸适配别再用固定像素思维写布局自由窗口和全屏最大的区别是窗口的宽高比例可以任意变化。这意味着你不能再依赖“屏幕是竖屏或横屏”的假设来写布局而必须过渡到“窗口有多宽我就渲染什么布局”的模型。Google 推荐的方案是利用尺寸断点。Jetpack WindowManager 提供了三个窗口尺寸类别Compact宽度小于 600dp、Medium600dp~840dp、Expanded大于 840dp。在自由窗口模式下窗口宽度会随着用户拖动在多个类别之间横跳布局必须能够响应这种变化。在 Compose 里可以用BoxWithConstraints或WindowSizeClass动态切换布局Composable fun AppContent(windowSize: WindowWidthSizeClass) { when (windowSize) { WindowWidthSizeClass.Compact - CompactLayout() WindowWidthSizeClass.Medium - MediumLayout() WindowWidthSizeClass.Expanded - ExpandedLayout() } }在 View 体系里除了准备layout-w600dp、layout-w840dp等资源目录还要确保代码里没有把某个固定 dp 写死。我排查过不少自由窗口下的布局 Bug最后发现根因都是“if (width 600) 判定一次后缓存了结果之后窗口变窄不重新计算”。记住自由窗口下的尺寸变化是常态任何缓存尺寸的行为都是隐患。5.4 输入法与窗口焦点自由窗口独有的交互陷阱自由窗口模式下一个很让人抓狂的场景是用户点击窗口 A 的输入框弹出了键盘然后又点了一下窗口 B想让 B 获得焦点结果键盘跟着焦点跑到了 B 下面或者键盘不消失、继续遮挡 A 的内容。这个问题涉及 IME 的窗口焦点管理当焦点在多个自由窗口之间切换时IME 需要正确地附着到当前焦点窗口上。对应用而言能做的事情有两个一是主动处理OnWindowFocusChangeListener在窗口失焦时隐藏输入法或收起相关 UI 状态二是利用WindowInsetsCompat准确感知 IME 是否与当前窗口重叠。这里有一个我踩过的小坑如果窗口失焦时不处理键盘输入法仍会悬停在窗口上方用户会觉得界面被“卡住”了。正确的做法是在失焦回调里通过InputMethodManager主动隐藏键盘override fun onWindowFocusChanged(hasFocus: Boolean) { super.onWindowFocusChanged(hasFocus) if (!hasFocus) { val imm getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm.hideSoftInputFromWindow(windowToken, 0) } }这只是自由窗口下交互细节的一个缩影。实际上窗口焦点、触摸事件竞争、任务快照更新这些环节在自由窗口里都会表现出与全屏完全不同的复杂度需要开发者一个个去调。5.5 典型问题速查表症状可能原因处理方式应用无法进入自由窗口设备未开启 freeform 支持或 ROM 默认关闭确认全局开关、开发者选项用模拟器验证窗口无法拖动或缩放应用内注册了自定义 Touch 拦截或窗口类型受限检查根布局的 OnTouchListener避免拦截系统拖动事件布局在窗口调整后不刷新使用了缓存尺寸或未处理尺寸回调改用WindowMetrics 注册尺寸监听强制重组/重绘输入法遮挡输入框IME insets 未处理或windowSoftInputMode设置不当使用WindowInsetsCompat.Type.ime()动态调整 padding窗口切换后状态丢失未做实例状态保存或configChanges与状态恢复冲突建立onSaveInstanceState ViewModel SavedStateHandle 三层恢复链窗口缩到很小时 UI 错乱未设置最小尺寸或布局对窄宽度不敏感设置 minWidth/minHeight使用尺寸断点布局多窗口下运行卡顿高耗内存应用被系统频繁回收或渲染开销过大优化内存占用避免在后台窗口维持高频动画6. 聊几句生态与未来的实话6.1 Freeform 到底解决谁的什么问题聊到这儿还是得回到一个朴素的问题自由窗口到底给普通用户带来了什么在手机上它确实不是刚需一块六寸多的屏幕塞多个窗口本来就勉强。但在平板、折叠屏、以及未来的车载大屏和便携屏上自由窗口解决的是真正的生产力诉求——一边开文档查资料一边写笔记一边看视频一边回消息一边对照表格一边开浏览器搜索。这些场景在桌面系统上很自然以前在 Android 大屏上却一直被分屏的僵硬模式限制着。从演进方向看自由窗口正在成为 Android 大屏设备的基本交互单元。Google 没有重新发明一套窗口系统而是把一个原本半成品的隐藏功能逐步打磨成标准能力并且通过 API 行为的收紧倒逼应用生态去配合。这个过程相当务实而且比很多厂商自己搞的那套私有多窗口方案要干净得多。6.2 厂商定制与第三方桌面的差异化空间尽管原生 Freeform 已经很能打但 ROM 厂商仍然有巨大的差异化空间。比如在窗口装饰上是保留系统的默认标题栏还是做一套自己的窗口控制按钮在交互手势上如何定义从屏幕边缘拖出自由窗口的手势在多窗口启动后的窗口排列规则上是强制平铺还是允许自由叠放在任务栏和 Dock 栏的整合上怎么和自家应用生态联动。这些都是厂商可以在 AOSP 基础上做出的用户体验差异化而且随着 Android 16、17 把底座做得更稳厂商从上个版本遗留的那些 dirty hack 里解放出来反而能把精力真正放到体验打磨上。6.3 给想跟进这个方向的开发者一个建议最后结合我这几年跟自由窗口和多窗口适配打交道的一线体会给正在做平板、折叠屏、大屏应用的朋友一个明确建议现在就开始把应用当做一个“窗口可以随时变化”的应用来设计。不要等到系统强制要求才动手。先把resizeableActivity打开把布局基于窗口尺寸来组织把状态保存链路做好把 IME insets 处理正确把WindowMetrics作为获取尺寸的唯一通道——这套动作在 14 到 17 的每个版本上都适用做完之后你会发现不光自由窗口没问题分屏、折叠屏展开、外接显示器这些场景也全都顺手解决了。自由窗口演进的本质是在逼着整个 Android 生态走向更成熟的桌面级体验早点跟上总比被系统拖着走舒服。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →