news 2026/9/7 11:16:35

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 11(API 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:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:resizeableActivity="true">

如果resizeableActivity为 false,横竖屏切换会有一些特殊限制,分屏入口直接消失。需要注意:这个属性只在早期 Android 版本中有效。从 Android 12 开始,系统基本忽略了这个属性,强制要求面向 Android 12 以上系统的应用支持多窗口。不过我们这里目标版本是 Android 11,所以显式配置仍然有意义。

2.2 screenOrientation 与分屏的矛盾

另一个容易踩坑的是android:screenOrientation。分屏模式下,窗口是可以横向也可以纵向的,如果你的 Activity 强制指定了portraitlandscape,在分屏入口检测时系统会认为该页面不支持旋转,有时会导致无法进入分屏,或者进入后布局诡异。

我之前遇到一个实际案例:一个视频播放页面固定了横屏,结果在 Android 11 平板上想分屏一边看视频一边记笔记,死活拉不出来分屏入口,最后把screenOrientation="landscape"去掉,改成由用户通过系统自动旋转控制,问题才消失。

如果你的应用业务上确实需要固定方向,建议在 Manifest 中就不要声明分屏支持,而是通过代码动态判断当前是否在多窗口模式下,再决定方向锁定的策略。简单说:分屏模式和 fixed orientation 是互斥的,不要同时出现在一个 Activity 上。

2.3 configChanges 配置

多窗口模式切换时,Activity 的系统配置会发生一系列变化,涉及屏幕尺寸、可用宽高、密度等。如果你的 Activity 没有声明android:configChanges,系统默认会销毁重建 Activity 来应用新配置。这个行为对很多应用会造成体验断裂——比如正在播放的视频断了、表单内容丢失。

在分屏 Demo 里,我建议至少声明以下配置:

<activity android:name=".MainActivity" android:exported="true" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboardHidden|density" android:launchMode="singleTask">

这样当用户从全屏切换到分屏,或者调整分屏分隔条位置时,Activity 不会重建,而是回调onConfigurationChanged,我们可以在代码里做自适应调整。

不过也要提醒一句:configChanges声明了之后,很多系统行为就要自己接管,比如横竖屏布局切换时,res/layout-land这样的资源目录不会自动生效,需要手动加载对应布局或者使用 ConstraintLayout 这种自适应布局来兜底。

2.4 exported 和 taskAffinity

如果你是通过 adb 命令或者在另一个 App 中启动分屏,需要确保目标 Activity 的android:exported="true"。这是 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 才能看到效果。我创建了MainActivitySecondActivityMainActivity作为 App 的主入口,SecondActivity作为分屏配对场景的另一半。

MainActivity的布局很简单,一个 TextView 显示当前窗口尺寸,两个 Button,一个用来打开第二屏 Activity,一个用来演示主动请求分屏。

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="vertical"> <TextView android:id="@+id/tv_info" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="16sp" /> <Button android:id="@+id/btn_open_second" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="打开第二个页面" /> <Button android:id="@+id/btn_split" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="主动请求分屏" /> </LinearLayout>

SecondActivity基本结构相同,只是颜色换成蓝绿色,方便在屏幕上区分两个页面。

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_ADJACENTFLAG_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 刷新交给onConfigurationChangedonResume去做,这样能避免大多数时序问题。

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_width="400dp",分屏后一半屏幕宽度可能只有 350dp,这块 View 就会溢出屏幕。所以多窗口布局的核心设计原则是:尽量使用match_parentwrap_contentweight、ConstraintLayout 百分比约束等方式,避免硬编码尺寸。

4.3 activity 重建与状态保存

虽然我们用configChanges避免了重建,但你不能保证所有设备都会遵循这个配置。某些厂商深度定制的系统在分屏切换时仍然会销毁重建 Activity,所以onSaveInstanceStateonRestoreInstanceState依然是必备保底方案。

在 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|Bounds"

Windows 信息里会显示每个窗口的边界,如果两个窗口的 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最近任务里没有分屏按钮显式设置为 true
Activity 固定屏幕方向长按图标没有出现分屏选项去掉 screenOrientation 或改用自动旋转
targetSdkVersion过低系统以兼容模式处理,行为异常targetSdk 指向 30
厂商定制系统限制某些平板/电视没有分屏入口升级系统或换原生设备测试

实测中最容易忽视的是屏幕方向这个点。我有一个项目,Activity 在 Manifest 中写了screenOrientation="portrait",在 Pixel 模拟器上分屏正常,换到某品牌的 Android 11 手机上,分屏功能直接消失。排查了半天,最后把方向属性去掉就解决了。原理是:分屏时窗口方向跟随系统,使用 fixed orientation 的应用被系统判定为无法适应多窗口环境。

6.2 分屏后布局错乱或控件重叠

分屏后窗口变窄,如果布局里用了很多固定宽度的 View,很容易错乱。我的经验是分屏调试时优先检查两个方面:

第一,是否存在硬编码尺寸。拿上面 Demo 举例,把TextViewtextSize固定在 16sp 一般没问题,但有些开发者把 Button 的宽度写成300dp,在窄窗口场景下就溢出了。改成match_parent加左右 margin,或者用 ConstraintLayout 的app:layout_constraintWidth_percent比例约束,基本可以解决。

第二,是否有ScrollView包裹自适应内容。分屏宽度变窄后,内容高度可能会变长,没有 ScrollView 包裹就会导致控件被压缩或者看不见。建议在分屏变化时动态显示或隐藏部分非核心 UI。

另一个细节是 Safe Area 的问题。某些设备在分屏状态下的系统导航栏会占用一部分空间,如果布局没有正确处理 insets,底部按钮可能被导航栏挡住。可以在 Activity 中监听 WindowInsets:

WindowCompat.setDecorFitsSystemWindows(window, false) ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.root_layout)) { _, insets -> val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) findViewById<View>(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) return@setOnClickListener lastClickTime = now // startActivity }

如果崩溃,日志中能看到IllegalStateException: Can't compress the activity或者ActivityNotFoundException,前者通常是因为系统认为当前窗口状态不支持分屏,比如屏幕已经在分屏模式但新 Activity 设置了不支持分屏的属性。检查 Manifest 中两个 Activity 是否都配置了resizeableActivity="true"

6.4 分屏模式下软键盘挤压布局

多窗口模式下输入法弹出,会把窗口高度进一步压缩,如果用的是adjustResize,布局会重新调整;如果用的是adjustPan,窗口会自动平移,两种模式在分屏场景下都容易引发视觉错乱。

一个稳妥的做法是在configChanges中声明screenSize,并在onConfigurationChanged里获取新的键盘可见区域,动态调整滚动位置。业务代码里不要假设键盘弹出后窗口高度不变。

我写的分屏 Demo 目前用的默认设置,没有做深度的键盘适配,但如果你做的是一个包含输入框的应用,强烈建议专门花时间测试分屏 + 键盘的场景,这个组合是分屏适配中被诟病最多的交互点。

7. 分屏 Demo 的扩展方向

分屏功能在实际项目里很少是孤立存在的,它往往和产品的多任务场景深度绑定。做完这个基础 Demo 后,可以继续往几个方向完善。

一个是分屏 + 数据交互。两个 Activity 分屏显示时,可以通过 ViewModel + LiveData 实现共享数据,也可以在两个页面之间互发广播。实现一个简单的数据同步,比如把MainActivity中的编辑内容实时显示在SecondActivity中,这样分屏就不仅仅是“两个页面同时显示”,而是真正实现了协作工作流。

另一个是分屏 + 多任务状态恢复。当用户旋转屏幕、拖动分隔条或退出分屏时,Activity 可能被重建或销毁。前面提到的onSaveInstanceState只是一种基础处理,更完整的做法是引入 ViewModel 配合 SavedStateHandle,让数据在整个生命周期中保持一致。

如果你想做更复杂的场景,可以在resizeableActivity基础上,尝试在宽窗口打开双面板布局——平板横屏时左边菜单右边内容,切到分屏窄窗口后自动变成单页模式。这种情况下,分屏功能的实现就不再是简单的配置问题,而是整个 UI 架构的适配优化。

最后提一个容易被忽略的点:分屏入口的用户引导。系统分屏手势在 Android 11 上还不算特别显眼,很多普通用户根本不知道应用可以分屏。如果你的应用主打生产力场景,可以在应用内做一个引导提示,教用户从最近任务进入分屏。这是产品层面的考虑,但和开发实现同样重要。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 11:16:01

2026国赛30天数学建模备赛全攻略:团队协作与真题实战

2026 年国赛&#xff08;全国大学生数学建模竞赛&#xff09;如果按往年的节奏来算&#xff0c;真正能完整利用的备赛时间&#xff0c;通常就是 30 天左右。这个周期不长不短&#xff0c;足够把一支队伍从“会建模、会写代码、会写论文”拉到“能在 72 小时内稳定产出完整论文”…

作者头像 李华
网站建设 2026/9/7 11:14:10

Claude Code安装配置与实战指南:AI编程助手深度集成开发

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 11:13:19

CMSIS-5源码深度解析:从Cortex-M内核到工程落地实践

做了这么多年嵌入式&#xff0c;说实话&#xff0c;真正敢拍着胸脯说“我把ARM官方那套CMSIS源码从头到尾啃完过”的人&#xff0c;不多。大多数时候我们都在用Keil、IAR或者CubeMX自动生成的工程&#xff0c;对着main函数里的while(1)一顿操作&#xff0c;却很少去想&#xff…

作者头像 李华