Android 11分屏功能Demo实战:配置、回调与避坑指南
简介这是一套面向Android开发者的分屏功能示例工程对应Android 11API 30的SplitScreenDemo项目演示分屏开启、关闭、任务切换以及设备/应用兼容性检测等完整流程。工程代码覆盖startActivityInSplitScreenMode()启动分屏、isDisplayCompatible()检查支持性、onMultiWindowModeChanged()监听分屏状态变化等关键API并包含主Activity、布局文件、事件监听器及辅助工具类同时提供可直接安装体验的debug APK。整套资源共514个文件以XML布局、Java源码、JSON配置、DEX/Class编译产物为主另有Gradle工程文件与APK安装包整体压缩后约13.91MB导入Android Studio即可查看运行。目前已有899人浏览学习适合有一定Android基础、希望快速掌握多窗口与分屏机制的开发者参考可直接借鉴其中的前后台任务调度思路规避分屏适配中的常见兼容性问题。1. Android 11分屏功能为什么值得单独做个Demo很多人对Android 11分屏的印象还停在“最近任务里把卡片拖到一侧”但真正开始写Demo时会发现系统判断的不是“你的Activity能不能分屏”而是一整套窗口模式、任务栈、配置变化的组合拳。一个只有几十个文件的分屏Demo能让你在半小时内看清这些机制也能把后续做Pad、折叠屏适配的底子打好。这篇笔记从manifest配置、最小工程、主动拉起的三种方式和常见踩坑讲起适合要上手多窗口的Android应用开发者也适合帮做系统集成的同学验证分屏行为。2. 分屏的前提配置窗口模式、resizeable与生命周期的关系2.1 窗口模式与任务栈分屏的底层不是“两个Activity用同一个布局”Android 11里的分屏本质是同一块屏幕上同时存在两个独立的Task每个Task都有自己的窗口栈Stack尺寸和边界。系统用WindowingMode来区分窗口是FullScreen、SplitScreenPrimary还是SplitScreenSecondary。Activity本身不直接挂在屏幕上而是挂进某个TaskTask再被分配给某个窗口模式。所以“我的Activity支不支持分屏”这个提问方式严格来说是错的正确的问法是“我的Task能不能够被系统调整尺寸”。理解这一点对排查特别有用。比如你只给Activity配了属性却没有考虑它所在的任务能否被重新布局分屏入口照样不会出现。反过来只要Task可调整大小Activity自己不去处理配置变化系统会在窗口尺寸改变时重建它。这就是很多分屏Demo第一版跑起来很顺畅、第二次拖动分割线就丢状态的根源。2.2 resizeableActivity不显式声明分屏入口直接灰掉resizeableActivity是manifest里控制分屏能力的核心开关。它写在application或者activity节点下系统根据它判断是否允许当前任务进入多窗口。如果写成false用户在各ROM的分屏入口处要么看不到你的应用要么看到入口但点击无反应少数ROM会把你的应用固定在原始尺寸其余区域留黑边。activity android:name.MainActivity android:exportedtrue android:resizeableActivitytrue /这个属性在Android 7.0到Android 11之间有过几轮默认值变化不同targetSdk版本下“没写”意味着“允许”还是“不允许”并不完全一致。我自己的习惯是凡是涉及分屏的Demo一律显式写成true不依赖系统默认判断。这样做的好处是行为可预期坏处是会让部分老设备的兼容测试暴露真实问题但那是后面要处理的坑不是配置阶段该省的功夫。2.3 screenOrientation、configChanges 与其他容易误配的开关分屏场景下screenOrientation是最容易误导人的属性。很多同学会写android:screenOrientationportrait来锁竖屏觉得分屏不会改变方向。实际上多窗口模式下系统允许用户把窗口拖到任意宽高比如果你的Activity声明了固定方向主流ROM的策略是忽略这个声明让窗口跟随可用空间变化少数系统则会直接拒绝分屏。configChanges则决定了你在窗口尺寸变化时是“自己处理”还是“交还给系统重建Activity”。分屏Demo里我建议把下面这一组都配进去activity android:name.MainActivity android:resizeableActivitytrue android:screenOrientationunspecified android:configChangeskeyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout /属性作用Demo建议值android:resizeableActivity是否允许进入多窗口trueandroid:screenOrientation屏幕方向限制多窗口下会被忽略unspecifiedandroid:configChanges配置变化是否交给Activity处理keyboardHidden / orientation / screenSize / smallestScreenSize / screenLayoutandroid:supportsPictureInPicture画中画能力容易和分屏混淆false这里再提醒一句supportsPictureInPicture和分屏没有直接关系它是画中画的开关。有人会把两者混在一起发现配了PIP就能分屏那是误打误撞因为两者都依赖resizeable或相关窗口模式不建议靠这种巧合写Demo。3. 最小Demo落地方案manifest、回调与第一次分屏3.1 工程骨架两个Activity一个package最小改动用Android Studio新建一个空工程包名建议写成com.example.splitscreen避免后面做自动化命令时记错路径。工程里只放两个ActivityMainActivity作为主入口SecondActivity作为分屏搭档。不需要引入任何第三方库也不需要额外的Module。实际开发中常见做法是先把MainActivity跑起来进入分屏后再从系统应用列表里选SecondActivity两个应用其实可以是同一个App内的不同页面。这也说明了一个现象分屏配对的任务并不要求来自不同应用同一App的两个Activity也能分屏只是系统UI入口需要它们都能出现在最近任务里。3.2 Manifest与布局用权重而非固定dp在MainActivity的manifest节点上把所有和分屏相关的配置写完整。注意targetSdkVersion最好指向30这样Android 11的分屏行为才完整生效如果项目里还是28或29系统可能按老规则处理部分回调。application android:labelSplitDemo activity android:name.MainActivity android:exportedtrue android:resizeableActivitytrue android:screenOrientationunspecified android:configChangeskeyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout / activity android:name.SecondActivity android:exportedtrue android:resizeableActivitytrue android:screenOrientationunspecified android:configChangeskeyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout / /applicationexported那行在Android 12之后是强制要求在Android 11上写了也不冲突属于向前兼容的好习惯。布局上我用两个TextView按weight平分宽度这样无论分屏窗口给多少空间左右两块都能各占一半。LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightmatch_parent android:orientationhorizontal TextView android:layout_width0dp android:layout_heightmatch_parent android:layout_weight1 android:background#4CAF50 android:gravitycenter android:textLeft Panel / TextView android:layout_width0dp android:layout_heightmatch_parent android:layout_weight1 android:background#FF9800 android:gravitycenter android:textRight Panel / /LinearLayout为什么不用固定dp宽度因为分屏后窗口宽度可能被压到220dp甚至更小固定dp会在横竖屏切换时露出边界而weight会跟着窗口尺寸自动缩放。分屏Demo最怕的不是逻辑复杂而是布局里残留下限尺寸到了小窗区域直接翻车。3.3 Activity回调处理onMultiWindowModeChanged 与 onConfigurationChangedAndroid 11里进入和退出分屏时系统会调用Activity.onMultiWindowModeChanged(boolean, Configuration)窗口尺寸连续变化时它会触发onConfigurationChanged(Configuration)。这两个回调是Demo最需要关心的点。public class MainActivity extends Activity { Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } Override public void onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig) { super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig); // 进入分屏和退出分屏都会走到这里 updateHint(newConfig); } Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); // 拖动分割线、旋转屏幕时这里会持续触发 if (isInMultiWindowMode()) { updateHint(newConfig); } } private void updateHint(Configuration config) { TextView hint findViewById(R.id.hint); if (hint null) { return; } String msg isMultiWindow isInMultiWindowMode() width config.screenWidthDp dp height config.screenHeightDp dp; hint.setText(msg); } }代码逻辑很简单isInMultiWindowMode()判断当前是否处于多窗口newConfig里的screenWidthDp和screenHeightDp实时反映窗口尺寸。注意onMultiWindowModeChanged里isInMultiWindowMode参数才是判断依据不要在onConfigurationChanged里用newConfig.orientation去猜是否分屏那样会把旋转屏幕和分屏混在一起。SecondActivity的代码可以完全复制只把布局颜色改一下用于区分两个窗口。3.4 第一次上机验证从Recent入口拖出分屏安装并启动MainActivity后手动验证路径如下确保MainActivity停留在前台。打开最近任务方式是底部上滑手势或按键输入。长按MainActivity的卡片弹出菜单后选择“分屏”。MainActivity会被放到上半屏下半屏出现应用选择区。从选择区选中SecondActivity完成配对。这条路径不需要写任何额外代码是最原始、最可靠的分屏入口。它依赖的是系统UI的Recents能力和我们后续要做的主动拉起方案不同。adb install -r app-debug.apk adb shell am start -n com.example.splitscreen/.MainActivity adb shell input keyevent KEYCODE_APP_SWITCH第一条命令安装第二条启动MainActivity第三条用按键事件打开最近任务。后面能不能正确弹出分屏入口取决于你的ROM是否有手势分屏能力。如果用的是原生Android 11模拟器长按卡片选择分屏的路径基本是稳定的但部分厂商ROM会把入口改到“应用信息”里这一步属于适配差异不算代码问题。4. 主动进入分屏的三种写法从adb命令、反射Options到系统UI入口4.1 先承认一个边界第三方应用没有“一键把别人拉入分屏”的公共APIAndroid 11虽然开放了多窗口机制但并没有给普通第三方应用提供一个公共API能够把任意两个App直接塞进分屏。系统UI里的“分屏”入口本质上是Launcher或SystemUI通过特权接口完成的普通应用没有这个权限。所以在设计Demo时要先确定你要演示的是“我的App能够适应分屏”还是“我的App能够主动发起分屏”。如果是后者且目标App不是你自己的那大概率只能走系统UI入口或adb辅助不要指望一段代码搞定。如果两个Activity都在你的进程里可操作性就高很多下面两种写法都是围绕这个场景展开的。4.2 反射 ActivityOptions.setLaunchWindowingMode在自己的进程里演示分屏ActivityOptions里其实有一个隐藏方法setLaunchWindowingMode(int)它接收的int值对应WindowingMode常量2代表SplitScreenPrimary3代表SplitScreenSecondary。因为这个m方法是hide的编译期不可见Demo里需要用反射调用。private void launchSecondAsSplit() { Intent intent new Intent(this, SecondActivity.class); try { ActivityOptions options ActivityOptions.makeBasic(); Method setMode ActivityOptions.class.getDeclaredMethod( setLaunchWindowingMode, int.class); setMode.setAccessible(true); // 返回值还是ActivityOptions可以链式继续构造 Object result setMode.invoke(options, 3); ActivityOptions finalOptions (ActivityOptions) result; startActivity(intent, finalOptions.toBundle()); } catch (Exception e) { Log.e(SplitDemo, setLaunchWindowingMode failed, e); // 降级普通启动保证主要流程不崩 startActivity(intent); } }这里的关键点是setLaunchWindowingMode的调用时机必须在startActivity之前并且第二个参数写3也就是Secondary窗口。MainActivity自身如果已经在FullScreen那SecondActivity会顶到它旁边形成一上一下的分屏效果。这段反射代码在原生AOSP分支上能走通厂商ROM如果对ActivityStarter加过壳可能拦截隐藏参数表现就是静默降级成普通启动。这类兼容性问题基本属于玄学遇到时不要死磕反射退回adb命令或系统UI入口。4.3 面向自动化验证的写法adb命令与uiautomator定位系统分屏入口自动化验证时我一般优先用adb命令而不是写死代码因为命令跑一遍就能知道系统认不认这个窗口模式。# 先以 FullScreen 启动 MainActivity adb shell am start --display 0 --windowing-mode 1 -n com.example.splitscreen/.MainActivity sleep 1 # 再用 Secondary 窗口模式启动 SecondActivity adb shell am start --display 0 --windowing-mode 3 -n com.example.splitscreen/.SecondActivity--windowing-mode 1代表全屏3代表分屏副窗口。命令执行后观察屏幕是否出现两个窗口如果只有一个说明当前系统或命令参数没被接纳。更贴近用户真实操作的验证方式是走系统UI入口先用uiautomator dump当前界面找到“分屏”按钮的坐标再用input tap点击。adb shell uiautomator dump /sdcard/window.xml adb shell cat /sdcard/window.xml | grep -o text[^]*分屏[^]*拿到坐标后点击再从应用选择区点选目标App。这条路虽然绕但它走的是系统真实入口能覆盖厂商ROM对分屏入口的改动也最接近用户行为。5. 分屏Demo避坑记录五个现象、原因与修复5.1 把API 30当成Q查配置现象按照Android 10的文档改manifest发现部分配置不生效分屏行为也不对。原因Android 10的代号是Q对应API 29Android 11的代号是R对应API 30。标题里的android11/Q其实是混用了两个版本的说法差一个版本分屏的默认行为和部分回调时机都不同。解决确认设备系统版本和targetSdkVersion都以API 30为基准resizeableActivity和configChanges按Android 11的规则写。Demo里把targetSdkVersion设为30不要用旧的Q行为来验证R的窗口模式。5.2 进/出分屏Activity被重建状态全没了现象从FullScreen拖进分屏界面重新走了一遍onCreate输入了一半的文本框内容消失。原因没有配置configChanges系统在窗口尺寸变化时销毁并重建Activity重建后没恢复瞬时状态。解决两个选择。缺省做法是在manifest里声明configChanges让Activity自己接收配置变化更稳的做法是引入ViewModel保存UI状态因为configChanges不能覆盖所有变化场景比如主题切换、语言变化这些仍然会导致重建。5.3 固定dp布局在分屏窗口里被挤扁现象分屏后按钮重叠文本溢出右侧区域基本不能看。原因布局用了固定dp宽度或盘点了windowManager.currentWindowMetrics导致适配只在全屏下成立。解决能横向伸缩的根布局用weight或ConstraintLayout百分比不能伸缩的控件在onConfigurationChanged里按当前可用宽高动态计算尺寸。窗口最小宽度在Android 11上由系统实际策略决定不要假设一定是320dp。5.4 触摸热点偏移点到的地方和看到的不一致现象分屏两个窗口都显示正常但点击上半屏时下半屏反而响应。原因常见于从第三方快捷启动器或自己反射启动分屏时窗口的输入区域没有被系统正确更新导致触摸映射错位。解决优先用系统UI入口验证如果必须用反射或adb方式检查目标Activity是否使用了透明主题或叠加了全屏Window。透明主题在分屏窗口模式下最容易触发热点偏移去掉后再试。5.5 分屏窗口被系统踢回FullScreen现象进入分屏几秒后其中一个窗口自动恢复全屏分屏配对失效。原因内存压力过高或目标应用不满足多窗口限制系统主动回收副窗口。解决先检查两个Activity的resizeableActivity是否为true再关掉后台大任务重新验证。低内存机型上分屏本就容易退出和代码关系不大。另外注意一些ROM会把“全屏游戏”和“分屏能力”分开处理别把厂商策略当成自己代码的bug。6. 用dumpsys与WindowMetrics验证分屏状态参数对照与宽高适配6.1 dumpsys window 与 dumpsys activity 对照验证分屏是否真正建立不能只看屏幕效果要落到窗口栈上。用dumpsys activity activities能看到的任务是TaskRecord级别而dumpsys window forces看的是Window级别。adb shell dumpsys activity activities | grep -E stackId|windowingMode|mActivityComponent adb shell dumpsys window windows | grep -E mAttrs|Window #|split-screen输出里会出现windowingMode字段值为2或3说明分屏窗口已建立值为1则是全屏。如果windowingMode正常但屏幕UI没分开问题大概率出在窗口动画或SystemUI渲染层反过来如果windowingMode还是1说明启动分屏的请求根本没被系统接受。配合systrace可以看回调时序抓分屏切换的过程在Trace里查找onMultiWindowModeChanged和onConfigurationChanged的触发点能确认Activity有没有及时响应窗口尺寸变化。这个验证方式对做系统集成的同学特别有用能区分“配置没下发”和“Activity没处理”两种故障。6.2 用WindowMetrics替换screenWidthDp处理宽高比变化Android 11上Configuration.screenWidthDp在某些情况下反映的是整个屏幕的配置而不是当前窗口的配置。做分屏宽高比适配时推荐直接查WindowManager的当前窗口边界。if (Build.VERSION.SDK_INT Build.VERSION_CODES.R) { WindowMetrics metrics getWindowManager().getCurrentWindowMetrics(); Rect bounds metrics.getBounds(); int widthPx bounds.width(); int heightPx bounds.height(); // 用 widthPx / heightPx 计算宽高比或动态调整布局 }getCurrentWindowMetrics返回的是当前Activity所在窗口的边界分屏状态下它就是被分割后的那个区域比Configuration里的数值可靠。Demo里最典型的用法是把宽高比打印出来再决定某个卡片要不要切换横竖形态。我自己的血泪经验是分屏Demo一旦跑通先别急着加功能花十分钟把这组dumpsys和WindowMetrics的验证脚本固定下来后期所有适配改动都拿同一组数据对照比肉眼观察靠谱得多。分屏的坑大多不是代码写错而是系统状态没看准。希望帮到你。本文还有配套的精品资源点击获取