ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Android 11 分屏适配实战:多窗口状态感知与动态布局切换

Android 11 分屏适配实战:多窗口状态感知与动态布局切换 简介面向Android 11及以上版本的应用开发者这份分屏功能实现项目演示了如何通过系统API开启、关闭分屏并在多窗口间切换适合有一定Android基础、想深入理解多任务机制的开发者学习。压缩包共514个文件约13.91MB以xml布局与资源配置、json数据、java源码及dex字节码为主同时包含gradle构建脚本、jar依赖和可直接安装的apk结构完整便于从源码工程层面拆解实现思路。项目围绕startActivityInSplitScreenMode()启动分屏、ActivityManager检查设备兼容性与任务切换、onMultiWindowModeChanged()监听窗口变化等关键点展开覆盖分屏生命周期的主要环节。目前已有896人学习这份资源。通过研究其布局文件、事件监听器和辅助工具类可快速搭建自己的分屏Demo也能在实际开发中迁移相关写法解决多窗口适配和切换问题。 分屏这个功能平时开发里很少会被当成一个独立模块来对待但只要你的应用真的被用户拖进分屏模式各种奇怪问题就会接二连三地冒出来。我最近基于 Android 11API 30整理了一套分屏适配 demo把多窗口下的状态感知、布局切换、尺寸适配和常见坑都过了一遍这篇博文就是这次实践的完整记录。如果你正打算给应用补上分屏支持或者想搞清楚 Android 11 的多窗口 API 到底怎么用可以重点看看后面的实操部分。1. Android 11 分屏 demo 到底该实现什么1.1 分屏的使用场景与系统行为分屏Split Screen是 Android 多窗口体系里最常用的一种形态核心价值就是让两个应用在同一个屏幕内同时可见、同时可操作。最常见的场景是边看视频边回消息、边查资料边记笔记、视频会议的同时打开文档。Android 11 在系统层面已经非常成熟手势导航下的分屏体验也做了不少优化从底部上滑进入最近任务点应用图标就能看到“分屏”入口。demo 需要覆盖的并不是“让系统强制进入分屏”这种系统级能力而是应用进入分屏之后如何让自己活得体面。也就是说这个 demo 要解决的是三个问题第一应用能否被系统允许进入分屏这由 Manifest 和 targetSdk 决定第二应用能否感知自己已经处于分屏状态这依赖isInMultiWindowMode这类 API第三应用能否在窗口尺寸剧烈变化后重新布局这需要配合onMultiWindowModeChanged和WindowMetrics来做。1.2 普通应用的权限边界能响应难强启在动手写代码之前先把权限边界说清楚否则你可能会搜索到一堆看着很牛但实际上跑不起来的代码。Android 系统确实有触发分屏的系统级 API比如ActivityTaskManager里的某些方法但这些接口基本都有平台签名或系统权限限制。普通应用通过公开 SDK 编译根本没有办法直接调用并弹出一个系统分屏。就算用反射强行调用大多数设备上也会因为没有权限而直接抛异常。真正靠谱的做法是把应用自身的适配做好然后由用户通过系统交互最近任务长按图标选“分屏”来触发。也有测试场景可以通过adb指令辅助进入分屏但不同厂商 ROM 的支持程度差异很大我在后面会单独讲。所以这个 demo 的定位非常明确它是一个“分屏适配 demo”不是“系统分屏启动器”。2. 分屏适配四个关键点从声明到布局切换2.1 声明 resizeableActivity别让应用“拒绝”分屏分屏适配的第一步是在 Manifest 中明确告诉系统你的 Activity 允许调整窗口大小。属性就是android:resizeableActivity它有两个值true表示允许进入多窗口模式false表示禁止。需要注意这个属性的默认值跟应用 targetSdk 有关。如果你的 targetSdk 大于等于 24默认值是true如果你的 targetSdk 低于 24系统会认为应用没有做过多窗口适配默认值是false。Android 11 上做 demo我建议直接把 targetSdk 设到 30 或 31然后显式声明activity android:name.MainActivity android:resizeableActivitytrue android:exportedtrue /如果你把resizeableActivity设成false系统会强制把该 Activity 放到全屏窗口里运行并自动调整尺寸以避免用户把应用拉进分屏后出现崩溃。这是兜底方案不建议常规使用因为它会让应用在多任务场景下直接“缺席”。Android 11 还允许通过meta-data或android:maxAspectRatio做一些更细的尺寸限制但在分屏 demo 里没有太大必要。2.2 用 isInMultiWindowMode 检测当前是否在分屏分屏状态下应用窗口的宽高比可能是 1:1、16:9甚至会被拉成一条细长的竖条。布局要想做出正确的响应第一步是准确判断自己是否处于多窗口模式。Activity.isInMultiWindowMode()是 API 24 加入的Android 11 上依然可用。它返回true时表示当前 Activity 正与其他 Activity 共享屏幕。override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) binding.tvMode.text if (isInMultiWindowMode) 当前分屏模式 else 当前全屏模式 }这个 API 在很多场景下非常好用。比如全屏播放视频的页面进入分屏后自动从横屏播放器切换成居中小窗聊天列表进入分屏后可以把列表的 item 间距压缩让一屏能展示更多内容。需要注意的是isInMultiWindowMode()只反映“当前是否有多个窗口”它不能告诉你“我是在上边还是下边”也无法告诉你分割线的具体位置。这些信息需要结合其他回调来获取。2.3 在 onMultiWindowModeChanged 里做布局切换当用户把应用拖入分屏、退出分屏或者拖动分割线改变窗口大小时系统会回调onMultiWindowModeChanged。Android 11 上有两个重载版本一个只有布尔参数另一个带Configuration参数。实际开发中建议重写带Configuration的版本因为在新配置里可以直接拿到最新的屏幕宽高、密度等信息避免再去手动查询。override fun onMultiWindowModeChanged(isInMultiWindowMode: Boolean, newConfig: Configuration) { super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig) binding.tvMode.text if (isInMultiWindowMode) 当前分屏模式 else 当前全屏模式 swapLayoutForMode(isInMultiWindowMode) }这里有一个非常容易踩的坑分屏变化不仅会回调onMultiWindowModeChanged还可能会触发onConfigurationChanged如果你的 Activity 声明了android:configChanges。不声明的话甚至可能直接触发 Activity 重建。所以布局刷新逻辑一定要设计成幂等的简单说就是同一个状态被回调两次也不能产生重复绑定或闪烁的效果。我在后面的常见问题里会再展开。2.4 用 WindowMetrics 拿到真实可用的窗口尺寸Android 11 引入了一组新的窗口尺寸获取方式WindowManager.getCurrentWindowMetrics()。它返回WindowMetrics对象里面包含bounds和windowInsets可以拿到当前窗口在考虑系统栏、刘海屏等遮挡之后的真实可用区域。fun getAvailableBounds(): Rect { return if (Build.VERSION.SDK_INT Build.VERSION_CODES.R) { windowManager.currentWindowMetrics.bounds } else { val display windowManager.defaultDisplay val point Point() display.getRealSize(point) Rect(0, 0, point.x, point.y) } }在分屏场景下窗口宽高和全屏时差距很大用DisplayMetrics或getRealSize拿到的往往是整个屏幕的物理尺寸容易导致布局计算出错。用WindowMetrics则是直接面向当前窗口适配起来更准。官方也在逐步推动旧 API 的弃用新项目建议从一开始就使用它。3. 上手搭一个可分屏的 demo 工程3.1 工程配置与目录demo 不需要太复杂我建了一个包含两个 Activity 的工程MainActivity作为首页入口SecondActivity作为第二个任务窗口。主界面放一个按钮和一个状态文本SecondActivity 放一段占位内容。这样最直观地演示“两个窗口并存”时的交互和数据传递。工程主要配置如下android { compileSdk 31 defaultConfig { applicationId com.example.splitscreendemo minSdk 26 targetSdk 30 versionCode 1 versionName 1.0 } }Manifest 中两个 Activity 都声明resizeableActivitytrue同时不设置固定的screenOrientation。这一步很关键如果某个 Activity 锁定了横屏或竖屏它在分屏模式下会被系统强制调整甚至无法正常参与分屏。src/main/java/com/example/splitscreendemo ├── MainActivity.kt └── SecondActivity.kt res/layout ├── activity_main.xml └── activity_second.xml3.2 MainActivity 状态感知核心代码核心逻辑写在 MainActivity 里。这里我用了 Kotlin和 Java 的逻辑完全一致只看你习惯哪种。class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) binding.tvMode.text if (isInMultiWindowMode) 当前分屏模式 else 当前全屏模式 binding.btnOpenSecond.setOnClickListener { startActivity(Intent(this, SecondActivity::class.java)) } } override fun onMultiWindowModeChanged(isInMultiWindowMode: Boolean, newConfig: Configuration) { super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig) binding.tvMode.text if (isInMultiWindowMode) 当前分屏模式 else 当前全屏模式 // 这里根据模式切换布局约束比如全屏用列表分屏用网格 val targetLayout if (isInMultiWindowMode) { R.layout.activity_main_split } else { R.layout.activity_main } // 简单方式直接重新 inflate 或使用 ConstraintLayout 的约束变化 if (binding.root.tag ! targetLayout) { Log.d(SplitDemo, switch to ${if (isInMultiWindowMode) split else full} layout) binding.root.tag targetLayout } } }这里我故意没有写完整的布局切换代码因为不同项目布局差异太大。实际开发中建议用ConstraintLayout配合百分比约束或者用两套layout目录而不是直接换根布局否则页面上已有的状态数据会比较难保留。3.3 手动验证步骤从最近任务进入分屏工程跑起来之后验证步骤如下。打开MainActivity确认界面完整显示点击按钮能跳转到SecondActivity再回到 MainActivity。从屏幕底部上滑进入最近任务列表。找到 MainActivity 的卡片点击右上角或顶部的应用图标在弹出的菜单中选择“分屏”。屏幕会自动分成上下两个区域下方会出现最近任务列表选择 SecondActivity。此时 MainActivity 和 SecondActivity 会同时显示在屏幕上下两侧tvMode文本应从“全屏模式”变成“分屏模式”。拖动中间分割线观察应用是否出现明显的卡顿、退出、布局错乱。整个过程中logcat 里SplitDemo标签下会输出布局切换日志可以用来确认回调是否正常触发。4. 触发分屏的几种姿势与边界4.1 用户手势触发普通用户触发分屏最标准的手势是从屏幕底部上滑进入最近任务点击应用图标选择“分屏”。部分 Android 11 设备还支持在最近任务里长按卡片直接拖到屏幕顶部或底部来快速分屏。这套交互是系统提供的应用侧无需任何处理只需保证自己可调整大小。如果你的应用没有声明resizeableActivitytrue那么在最近任务的菜单里可能不会出现“分屏”选项或者即便选择了分屏系统也会强制把它整屏显示。因此验证 demo 之前一定要先检查 Manifest 配置。4.2 adb 与测试机上的辅助手段做自动化测试时靠手势点击效率太低。Android 官方在adb中提供了一些窗口管理命令可以帮助开发者更快进入多窗口状态。最常见的做法是先在应用内启动两个 Activity再用am task相关命令调整窗口模式。比如下面这种形式adb shell am task split-screen但我要特别提醒这类命令在不同版本、不同厂商 ROM 上的可用性差异极大有的需要 shell 权限有的甚至需要 root。它更适合作为测试工程师在调试版设备上使用的辅助工具不能写在正式产品逻辑里。更稳妥的自动化方案是使用UiAutomator模拟用户点击最近任务中的分屏按钮虽然慢一点但更接近真实行为。4.3 ActivityOptions.setLaunchBounds 的局限很多人搜索“Android 分屏实现”时会看到ActivityOptions.setLaunchBounds(Rect)以为这是启动分屏的公开 API其实它只是设置新 Activity 的初始边界并不是系统分屏模式的开关。它的作用范围是在支持多窗口的设备上让新启动的 Activity 按你给定的Rect来决定窗口大小。你可以在代码里这样写val bounds Rect(0, 0, displayWidth / 2, displayHeight) val options ActivityOptions.makeBasic() options.setLaunchBounds(bounds) startActivity(intent, options.toBundle())但这段代码通常只会在支持自由窗口freeform的系统或特定设备上产生类似小窗的效果。在普通手机上系统是否真正采用你给的 bounds取决于设备本身的窗口策略而且它并不会触发基于系统任务的分屏模式。所以我的结论是setLaunchBounds可以作为窗口自定义大小的参考 API但不要指望它代替分屏功能。5. 分屏适配中的常见问题与避坑记录问题表现可能原因解决办法进入分屏后 Activity 重新创建页面状态丢失未处理配置变更系统重建 Activity使用 ViewModel 保存业务数据或用savedInstanceState恢复轻量状态分屏后布局挤压控件重叠使用固定宽高或依赖全屏尺寸计算改用WindowMetrics读取当前窗口真实尺寸配合ConstraintLayout的相对约束onMultiWindowModeChanged 触发多次界面闪烁布局刷新逻辑没有做幂等保护在回调里增加状态判断比如记录当前模式标签只有状态变化时才刷新软键盘弹出后遮挡输入框窗口 mode 变化后软键盘策略未调整在 Manifest 中设置windowSoftInputModeadjustResize并在 onConfigurationChanged 中重新计算面板高度分屏状态退出后页面没有恢复全屏布局只处理了进入分屏回退没处理退出回退保证布局切换在两个方向都生效isInMultiWindowModefalse时也要刷新视频播放页在分屏下黑屏或闪退Surface 和 TextureView 在窗口重建时生命周期没有处理好在 onStop 中释放播放器资源在 onStart 中重新初始化同时避免在分屏切换瞬间操作 Surface再补充几个我在这次 demo 中实际踩到的经验。一是不要在onMultiWindowModeChanged里做重量级操作比如重新setContentView或重建整个 Fragment。分屏变化过程中系统对性能会比较敏感重量级操作容易导致掉帧甚至 ANR。我的习惯是只做轻量换肤或局部 View 可见性切换真正复杂的状态放到ViewModel里统一管理。二是分屏状态下onResume的触发规则和全屏时不同。两个应用同时可见时只有获得焦点的那个会收到onResume另一个停留在onPause但不会onStop。很多人在分屏下写“离开界面就暂停播放”的逻辑结果发现用户一边看视频一边聊天时视频并没有暂停这就是因为在分屏模式下另一个 Activity 仍处于可见状态。判断是否需要暂停不能只看onPause最好结合isInMultiWindowMode来决定。三是如果应用里有全屏性质的功能比如相机预览、沉浸式阅读器、游戏画面进入分屏后一定要做功能降级而不是强行保持原来的交互。分屏窗口高度只有全屏一半时很多按钮的位置需要重新设计否则影响日常使用。最后再分享一个我个人的做法分屏适配不要等到测试阶段再做而是在开发每个新页面时就把isInMultiWindowMode的布局逻辑写进去。成本不算高但能提前暴露很多尺寸适配问题。遇到拿不准的视觉细节直接用分屏模式跑一遍比任何纸上讨论都有效。本文还有配套的精品资源点击获取
返回列表