十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Android 11分屏功能实现:从配置到代码实战

Android 11分屏功能实现:从配置到代码实战 简介Android 11/Q分屏功能实现Demo是一份面向Android开发者的示例工程演示了在Android 11系统中开启、关闭分屏以及在分屏模式下切换任务的具体实现适合需要适配多窗口或多任务场景的进阶开发者参考。压缩包共514个文件涵盖xml布局脚本、flat编译资源、java源代码、dex可执行文件、jar依赖库以及构建生成的apk等整体大小13.91MB目录结构清晰便于按模块查阅。该资源已有896人学习下载代码覆盖startActivityInSplitScreenMode()启动分屏、isDisplayCompatible()配合resizeableActivity检查设备与应用支持、moveTaskToBack()/moveTaskToFront()切换任务、getRunningTasks()获取当前任务、onMultiWindowModeChanged()监听分屏状态变化等关键环节同时包含主Activity、布局文件、事件监听器及辅助工具类开发者可据此快速理解Android 11分屏机制并直接用于项目改造或排错。1. 分屏功能实现前的整体思路Android 11API 30也就是常说的 R / Red Velvet Cake 版本在分屏和多窗口支持上已经非常成熟相比早期版本系统对窗口缩放、多实例、拖拽分屏的兼容性处理更完善。如果你做过 Android 7.0 时代的多窗口适配再回头看 Android 11会发现坑少了很多但也不是完全无脑就能跑起来。我这次实现的是一个最小可运行的分屏 Demo目标很简单两个独立的 Activity 能同时显示在屏幕上用户可以通过系统分屏入口将其中一个 App 拖到屏幕一侧再选择另一个 App 填充另一侧。听起来好像不算什么复杂需求但真要落地需要处理 manifest 配置、Activity 启动模式、生命周期回调、布局自适应等一系列问题。这个 Demo 适合谁看如果你正在做手机平板适配、车机应用开发或者需要让应用支持多窗口场景这篇内容可以直接作为起步模板。另外如果你手头有一个老项目想在 Android 11 上快速验证分屏表现但又不确定从哪下手下面的配置和代码基本已经覆盖了最常见的需求和坑。先看一下整体的实现路径配置 AndroidManifest让 Activity 支持分屏确认 targetSdkVersion 和屏幕方向等限制条件通过 Intent Flags 或 adb 命令主动触发分屏处理 onMultiWindowModeChanged 生命周期回调解决 Activity 在多窗口下尺寸变化后的布局适配问题常见崩溃和诡异现象的排查在动手之前有一点需要明确分屏模式不是系统默认对所有应用开放的。系统会根据resizeableActivity属性、targetSdkVersion 和屏幕方向设置来决定一个应用能否进入分屏。很多初学者在这里卡住App 明明写好了但分屏入口怎么都出不来十有八九就是resizeableActivity没有配置或者被设置成了 false。2. 核心配置与 Manifest 标签拆解2.1 resizeableActivity 属性分屏功能最核心的开关在 AndroidManifest.xml 的application或activity节点上就是android:resizeableActivity。这个属性告诉系统当前应用是否支持调整窗口尺寸。如果不设置默认值分情况——targetSdkVersion 在 Android 7.0 及以上时默认是 true但如果你在低版本 SDK 上升级上来的老项目有时候会有各种意外表现所以我建议无论如何都显式声明。application android:allowBackuptrue android:iconmipmap/ic_launcher android:labelstring/app_name android:resizeableActivitytrue如果resizeableActivity为 false横竖屏切换会有一些特殊限制分屏入口直接消失。需要注意这个属性只在早期 Android 版本中有效。从 Android 12 开始系统基本忽略了这个属性强制要求面向 Android 12 以上系统的应用支持多窗口。不过我们这里目标版本是 Android 11所以显式配置仍然有意义。2.2 screenOrientation 与分屏的矛盾另一个容易踩坑的是android:screenOrientation。分屏模式下窗口是可以横向也可以纵向的如果你的 Activity 强制指定了portrait或landscape在分屏入口检测时系统会认为该页面不支持旋转有时会导致无法进入分屏或者进入后布局诡异。我之前遇到一个实际案例一个视频播放页面固定了横屏结果在 Android 11 平板上想分屏一边看视频一边记笔记死活拉不出来分屏入口最后把screenOrientationlandscape去掉改成由用户通过系统自动旋转控制问题才消失。如果你的应用业务上确实需要固定方向建议在 Manifest 中就不要声明分屏支持而是通过代码动态判断当前是否在多窗口模式下再决定方向锁定的策略。简单说分屏模式和 fixed orientation 是互斥的不要同时出现在一个 Activity 上。2.3 configChanges 配置多窗口模式切换时Activity 的系统配置会发生一系列变化涉及屏幕尺寸、可用宽高、密度等。如果你的 Activity 没有声明android:configChanges系统默认会销毁重建 Activity 来应用新配置。这个行为对很多应用会造成体验断裂——比如正在播放的视频断了、表单内容丢失。在分屏 Demo 里我建议至少声明以下配置activity android:name.MainActivity android:exportedtrue android:configChangesscreenSize|smallestScreenSize|screenLayout|orientation|keyboardHidden|density android:launchModesingleTask这样当用户从全屏切换到分屏或者调整分屏分隔条位置时Activity 不会重建而是回调onConfigurationChanged我们可以在代码里做自适应调整。不过也要提醒一句configChanges声明了之后很多系统行为就要自己接管比如横竖屏布局切换时res/layout-land这样的资源目录不会自动生效需要手动加载对应布局或者使用 ConstraintLayout 这种自适应布局来兜底。2.4 exported 和 taskAffinity如果你是通过 adb 命令或者在另一个 App 中启动分屏需要确保目标 Activity 的android:exportedtrue。这是 Android 12 之后的一个硬性要求但在 Android 11 上提前加上没有任何坏处。taskAffinity在分屏场景中也有微妙影响。分屏中的两个 App 属于不同的 task如果你设置了不合理的 taskAffinity可能导致系统判断任务归属异常出现无法分屏或启动后自动全屏的奇怪情况。如果没有特殊需求保持默认即可不必为每个 Activity 单独指定。3. 分屏 Demo 的完整实现过程3.1 搭建基础工程我直接用 Android Studio 创建一个空工程包名com.example.splitdemo语言选 Java 或 Kotlin 都行。为了贴近大多数开发者的习惯下面以 Kotlin 为例。项目的build.gradle关键配置如下android { compileSdkVersion 30 defaultConfig { applicationId com.example.splitdemo targetSdkVersion 30 } }注意targetSdkVersion最好明确为 30否则你在 Android 11 真机上测试时系统会以兼容模式处理一些行为比如分屏入口的位置、手势方式可能和预期不一致。用compileSdkVersion 30配合targetSdkVersion 30实测下来最稳妥。3.2 编写两个分屏页面分屏至少要两个 Activity 才能看到效果。我创建了MainActivity和SecondActivity。MainActivity作为 App 的主入口SecondActivity作为分屏配对场景的另一半。MainActivity的布局很简单一个 TextView 显示当前窗口尺寸两个 Button一个用来打开第二屏 Activity一个用来演示主动请求分屏。?xml version1.0 encodingutf-8? LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightmatch_parent android:gravitycenter android:orientationvertical TextView android:idid/tv_info android:layout_widthwrap_content android:layout_heightwrap_content android:textSize16sp / Button android:idid/btn_open_second android:layout_widthwrap_content android:layout_heightwrap_content android:text打开第二个页面 / Button android:idid/btn_split android:layout_widthwrap_content android:layout_heightwrap_content android:text主动请求分屏 / /LinearLayoutSecondActivity基本结构相同只是颜色换成蓝绿色方便在屏幕上区分两个页面。3.3 启动第二个页面的逻辑第一个按钮的逻辑比较基础就是直接startActivity打开第二个 Activity。如果当前系统处于分屏模式这个 Activity 会在另一侧打开如果处于全屏模式则会把当前页面覆盖与普通跳转没有区别。不过要留意一点打开第二个 Activity 时如果递了 Intent Flag系统的表现会不同。常规写法不需要任何 Flag只需要让系统自己决定窗口归属btnOpenSecond.setOnClickListener { val intent Intent(this, SecondActivity::class.java) startActivity(intent) }如果直接这样跑你会发现一个现象点击第二个按钮打开SecondActivity后它确实进入了分屏但如果你用两个不同的 App 做分屏比如系统和另一个应用启动方式就复杂一些后面会单独用一节来讲。3.4 代码触发分屏的 Intent Flags除了让用户手动从最近任务里拖拽进入分屏开发者也可以主动请求进入分屏。核心是使用系统提供的 Multi-Window Flags在 Intent 中加入FLAG_ACTIVITY_LAUNCH_ADJACENT和FLAG_ACTIVITY_MULTIPLE_TASK。btnSplit.setOnClickListener { val intent Intent(this, SecondActivity::class.java) intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT) intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK) startActivity(intent) }这两个 Flag 的作用是FLAG_ACTIVITY_LAUNCH_ADJACENT要求新 Activity 尽量在当前 Activity 旁边打开即分屏FLAG_ACTIVITY_MULTIPLE_TASK允许新 Activity 在一个新的 task 中启动。两者组合使用是 Android 7.0 以来代码触发分屏的标准姿势。但实际的触发效果受很多因素限制包括当前设备是否支持分屏、系统是否处于电视模式、App 是否配置了 resizeableActivity 等。真机上测试时这个按钮在部分设备上可能没有任何效果这是正常的后面我会专门讲讲调试方法。4. 生命周期回调与多窗口模式适配4.1 onMultiWindowModeChanged 回调进入或退出分屏模式时系统会给 Activity 发回调onMultiWindowModeChanged(boolean isInMultiWindowMode)。这个方法在 Activity 可以显示的时候被调用参数表示当前是否处于多窗口模式。在这个回调里需要做的事情一般是重新读取窗口尺寸、调整布局逻辑、处理一些需要全屏才有的 UI 元素。踩过的坑是这个回调在 Activity 创建早期就可能触发一次如果你的代码里在此回调中访问了尚未初始化完成的 View会抛出空指针。稳妥的做法是加一个标志位判断或者把 UI 更新逻辑放到onWindowFocusChanged中统一处理。我自己的经验是在onMultiWindowModeChanged里只保存模式状态和一个布尔值具体的 UI 刷新交给onConfigurationChanged和onResume去做这样能避免大多数时序问题。override fun onMultiWindowModeChanged(isInMultiWindowMode: Boolean, newConfig: Configuration) { super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig) multiWindowMode isInMultiWindowMode if (isInMultiWindowMode) { // 进入分屏模式 } else { // 退出分屏模式恢复全屏布局 } }4.2 Configuration 变化处理onConfigurationChanged是另一个高频回调。分屏分隔条拖动过程中窗口的 available width 和 height 会频繁变化系统会不断产生 Configuration 变化事件。声明了configChanges之后这个回调承担了原本由重建 Activity 完成的资源切换工作。你需要在这里更新布局参数或者重新设置某些 View 的尺寸。如果布局只用 LinearLayout match_parent 这种相对简单的方式大部分情况下不需要额外处理。但如果你在布局中使用了固定宽度比如某个View强制layout_width400dp分屏后一半屏幕宽度可能只有 350dp这块 View 就会溢出屏幕。所以多窗口布局的核心设计原则是尽量使用match_parent、wrap_content、weight、ConstraintLayout 百分比约束等方式避免硬编码尺寸。4.3 activity 重建与状态保存虽然我们用configChanges避免了重建但你不能保证所有设备都会遵循这个配置。某些厂商深度定制的系统在分屏切换时仍然会销毁重建 Activity所以onSaveInstanceState和onRestoreInstanceState依然是必备保底方案。在 Demo 里我在MainActivity中保存了当前显示文本在重建后恢复override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putString(info, tvInfo.text.toString()) } override fun onRestoreInstanceState(savedInstanceState: Bundle) { super.onRestoreInstanceState(savedInstanceState) savedInstanceState.getString(info)?.let { tvInfo.text it } }很多开发者写过类似代码但在分屏场景下有一个特殊之处分屏模式的切换非常频繁每次拖动分隔条都触发一次onSaveInstanceState也不奇怪。如果保存的数据过大或者恢复逻辑耗时较长用户会感觉到明显卡顿。所以建议只保存轻量级数据复杂对象用 ViewModel 保存。5. 通过 adb 命令测试分屏功能5.1 adb 分屏命令解析如果你的 App 不支持代码主动触发分屏或者你是第三方应用无法干预它的入口可以用 adb 命令来测试。这个方式对开发调试特别有用因为不需要改代码就能验证任意 App 在分屏模式下的表现。先启动一个 App然后执行命令adb shell am start -n com.example.splitdemo/.MainActivity接着把另一个 App 放到分屏另一侧。在 Android 11 上可以模拟用户手势adb shell settings put global enable_freeform_support 1 adb shell settings put global enable_freeform_support 0但更直接的方式是通过am start的参数。启动一个 Activity 并添加--windowingMode参数可以强制指定窗口模式adb shell am start -n com.example.splitdemo/.SecondActivity --windowingMode 3这里3表示分屏模式Multi-Window。这个命令在部分 Android 11 原生和类原生设备上是有效的但在厂商定制系统上可能被忽略。更通用的方式是adb shell am start -n com.example.splitdemo/.SecondActivity adb shell am start -n com.example.splitdemo/.MainActivity在最近任务里手动拖动第一个卡片到屏幕上方的高亮区域再选择第二个卡片——这套手势模拟在自动化测试脚本里很难完全模拟所以更多是人工测试时使用。5.2 dumpsys 查看分屏状态验证当前是否处于分屏模式以及窗口的 bounds用dumpsys命令比较方便adb shell dumpsys window | grep -E mCurrentFocus|mFocusedApp|BoundsWindows 信息里会显示每个窗口的边界如果两个窗口的 bounds 加起来正好是屏幕的完整范围就说明你处于分屏状态。我在调试中经常用这个命令快速确认问题出在哪个环节。比如点击按钮后没有进入分屏先看dumpsys window输出判断是配置问题Activity 不支持分屏还是信号问题Intent Flag 被系统忽略再针对性地改代码。5.3 freeform 与分屏的区别Android 11 同时支持分屏和自由窗口Freeform但两者的判定机制完全不同。自由窗口是可以随意拖动、任意缩放的浮动窗口分屏则是固定在屏幕左右或上下两侧的固定比例窗口。部分 Mini 尺寸设备不支持自由窗口模式但分屏一般都有。在 Android 11 上开启自由窗口模式的命令adb shell settings put global enable_freeform_support 1但如果你只是做分屏 Demo不建议开启这个开关因为自由窗口的出现会让调试行为变得更混乱尤其在做布局自适应测试时窗口尺寸变化更大更容易暴露问题。6. 常见问题与调试排查实录6.1 分屏入口消失或无法进入分屏这个是最常见也是最先遇到的问题。可能性逐一排查问题原因表现形式解决方案resizeableActivity未设置或为 false最近任务里没有分屏按钮显式设置为 trueActivity 固定屏幕方向长按图标没有出现分屏选项去掉 screenOrientation 或改用自动旋转targetSdkVersion过低系统以兼容模式处理行为异常targetSdk 指向 30厂商定制系统限制某些平板/电视没有分屏入口升级系统或换原生设备测试实测中最容易忽视的是屏幕方向这个点。我有一个项目Activity 在 Manifest 中写了screenOrientationportrait在 Pixel 模拟器上分屏正常换到某品牌的 Android 11 手机上分屏功能直接消失。排查了半天最后把方向属性去掉就解决了。原理是分屏时窗口方向跟随系统使用 fixed orientation 的应用被系统判定为无法适应多窗口环境。6.2 分屏后布局错乱或控件重叠分屏后窗口变窄如果布局里用了很多固定宽度的 View很容易错乱。我的经验是分屏调试时优先检查两个方面第一是否存在硬编码尺寸。拿上面 Demo 举例把TextView的textSize固定在 16sp 一般没问题但有些开发者把 Button 的宽度写成300dp在窄窗口场景下就溢出了。改成match_parent加左右 margin或者用 ConstraintLayout 的app:layout_constraintWidth_percent比例约束基本可以解决。第二是否有ScrollView包裹自适应内容。分屏宽度变窄后内容高度可能会变长没有 ScrollView 包裹就会导致控件被压缩或者看不见。建议在分屏变化时动态显示或隐藏部分非核心 UI。另一个细节是 Safe Area 的问题。某些设备在分屏状态下的系统导航栏会占用一部分空间如果布局没有正确处理 insets底部按钮可能被导航栏挡住。可以在 Activity 中监听 WindowInsetsWindowCompat.setDecorFitsSystemWindows(window, false) ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.root_layout)) { _, insets - val systemBars insets.getInsets(WindowInsetsCompat.Type.systemBars()) findViewByIdView(R.id.btn_split).setPadding(0, 0, 0, systemBars.bottom) insets }6.3 启动 SecondActivity 时黑屏或崩溃黑屏通常发生在快速双击按钮、连续触发startActivity的场景。原因可能是 Activity 还未完成初始化又被新的启动 Intent 覆盖。在分屏状态下这种问题更明显因为窗口在创建过程中要经历布局测量时间更长。我的处理办法是在启动前加一点防抖判断禁止短时间内重复跳转private var lastClickTime 0L btnOpenSecond.setOnClickListener { val now System.currentTimeMillis() if (now - lastClickTime 500) returnsetOnClickListener lastClickTime now // startActivity }如果崩溃日志中能看到IllegalStateException: Cant compress the activity或者ActivityNotFoundException前者通常是因为系统认为当前窗口状态不支持分屏比如屏幕已经在分屏模式但新 Activity 设置了不支持分屏的属性。检查 Manifest 中两个 Activity 是否都配置了resizeableActivitytrue。6.4 分屏模式下软键盘挤压布局多窗口模式下输入法弹出会把窗口高度进一步压缩如果用的是adjustResize布局会重新调整如果用的是adjustPan窗口会自动平移两种模式在分屏场景下都容易引发视觉错乱。一个稳妥的做法是在configChanges中声明screenSize并在onConfigurationChanged里获取新的键盘可见区域动态调整滚动位置。业务代码里不要假设键盘弹出后窗口高度不变。我写的分屏 Demo 目前用的默认设置没有做深度的键盘适配但如果你做的是一个包含输入框的应用强烈建议专门花时间测试分屏 键盘的场景这个组合是分屏适配中被诟病最多的交互点。7. 分屏 Demo 的扩展方向分屏功能在实际项目里很少是孤立存在的它往往和产品的多任务场景深度绑定。做完这个基础 Demo 后可以继续往几个方向完善。一个是分屏 数据交互。两个 Activity 分屏显示时可以通过 ViewModel LiveData 实现共享数据也可以在两个页面之间互发广播。实现一个简单的数据同步比如把MainActivity中的编辑内容实时显示在SecondActivity中这样分屏就不仅仅是“两个页面同时显示”而是真正实现了协作工作流。另一个是分屏 多任务状态恢复。当用户旋转屏幕、拖动分隔条或退出分屏时Activity 可能被重建或销毁。前面提到的onSaveInstanceState只是一种基础处理更完整的做法是引入 ViewModel 配合 SavedStateHandle让数据在整个生命周期中保持一致。如果你想做更复杂的场景可以在resizeableActivity基础上尝试在宽窗口打开双面板布局——平板横屏时左边菜单右边内容切到分屏窄窗口后自动变成单页模式。这种情况下分屏功能的实现就不再是简单的配置问题而是整个 UI 架构的适配优化。最后提一个容易被忽略的点分屏入口的用户引导。系统分屏手势在 Android 11 上还不算特别显眼很多普通用户根本不知道应用可以分屏。如果你的应用主打生产力场景可以在应用内做一个引导提示教用户从最近任务进入分屏。这是产品层面的考虑但和开发实现同样重要。本文还有配套的精品资源点击获取
返回列表