多窗口模式从Android 7.0引入后,Activity的生命周期不能再简单按可见与否来推断。一个典型的例子是,在分屏状态下点击另一个窗口,当前Activity会收到onPause回调,但它的视图仍然有一部分或全部可见。此时如果开发者在onPause里直接释放播放器、关闭相机或者清空画布,就会导致界面仍在展示时资源已经被回收,用户体验非常割裂。理解多窗口生命周期,核心是区分焦点状态和可见状态,并掌握系统在多窗口切换时引入的新回调。

一、为什么多窗口让生命周期判断变得复杂
在单窗口任务模型中,一个Activity从前台进入后台的过程通常可以简化为:可见时会走到onStart和onResume,不可见时会走onPause和onStop。这套对应关系在大多数场景成立,因为失去焦点往往伴随着完全不可见。但在多窗口模式下,系统允许多个Activity同时处于可见区域,焦点却只能由一个窗口持有。因此,onResume与onPause这对回调代表的是窗口是否获得输入焦点,而onStart与onStop这对回调才代表Activity是否仍然对用户可见。
以分屏为例,上下两个应用同时显示。当前应用在上半屏时,用户点击下半屏输入文字,上半屏的Activity会立即执行onPause,但不会执行onStop,因为仍然可见。只有当用户将分割条拖到把上半屏完全挤出屏幕,或者启动了一个全屏Activity覆盖时,onStop才会触发。这种变化意味着过去常用的在onPause释放资源的策略需要调整。
| 场景 | 主要回调 | 是否仍然可见 |
|---|---|---|
| 点击另一个分屏窗口 | onPause | 可见 |
| 重新获得窗口焦点 | onResume | 可见 |
| 分屏中被完全遮挡 | onPause、onStop | 不可见 |
| 退出分屏回到全屏 | onMultiWindowModeChanged、onResume等 | 可见 |
二、onMultiWindowModeChanged回调的触发时机与用途
onMultiWindowModeChanged是Android 7.0为多窗口场景专门增加的回调。它在Activity进入或退出多窗口模式时被调用,早期版本只有一个布尔参数isInMultiWindowMode,Android 11开始增加了新的Configuration参数,用来携带窗口尺寸、边界、方向等更详细的信息。开发者可以在这个回调里动态调整布局,比如从宽屏分屏切换到窄屏画中画时,重新加载一套适合小窗口的界面资源。
override fun onMultiWindowModeChanged(isInMultiWindowMode: Boolean, newConfig: Configuration) {
super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig)
if (isInMultiWindowMode) {
// 进入多窗口:精简侧边栏,可能隐藏广告位
binding.sidebar.visibility = View.GONE
} else {
// 退出多窗口:恢复完整布局
binding.sidebar.visibility = View.VISIBLE
}
}
需要特别注意的是,onMultiWindowModeChanged可能晚于onConfigurationChanged触发,也可能伴随Activity重建。如果manifest中声明了configChanges来阻止重建,就要在这个回调里处理所有与窗口尺寸、方向变化相关的UI更新。如果没处理,系统会默认销毁并重建Activity,此时onSaveInstanceState和onRestoreInstanceState仍然会执行,但会导致状态保存成本更高。
为了减少不必要的重建,可以在 <activity> 节点中声明屏幕尺寸和方向相关的配置变更,让Activity自行处理而不是被系统销毁。如下所示:
<activity
android:name=".MainActivity"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:supportsPictureInPicture="true" />
三、分屏、自由窗口和画中画的生命周期差异
多窗口并不是单一模式,至少包含分屏、自由窗口和画中画三种。它们的生命周期行为并不完全相同。分屏模式下窗口并排,Activity大多数时间保持可见,切焦点只触发onPause。自由窗口模式允许用户像桌面端一样拖动窗口大小,窗口尺寸变化频繁,如果声明了configChanges,可能只收到onConfigurationChanged和onMultiWindowModeChanged,不会销毁重建。画中画模式更为特殊,进入画中画时Activity会进入暂停状态,并在小窗中继续渲染视频等内容。
画中画切换时,系统会调用onPictureInPictureModeChanged回调。进入画中画后,Activity通常处于onPause状态,但视频播放还必须继续。这意味着播放控制要放在onPause之外的地方,或者使用前台Service与MediaSession配合,而不能简单地在onPause里暂停播放。对于纯内容展示页面,进入画中画可能意味着需要隐藏所有交互控件,只保留最小化的播放窗口。
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
if (isInPictureInPictureMode) {
binding.controls.visibility = View.GONE
} else {
binding.controls.visibility = View.VISIBLE
}
}
分屏与画中画的另一个关键差异在于用户交互权限。分屏中的另一个窗口可以正常接收输入事件,而画中画窗口通常只提供有限的控制,例如播放和暂停按钮。因此,Activity在画中画模式下的生命周期虽然类似暂停,但系统允许媒体播放继续进行,这进一步说明了生命周期回调与业务状态解耦的必要性。
四、多窗口模式下保存状态和释放资源的正确姿势
多窗口场景下,onPause仍然是可靠的最后焦点丢失回调,适合保存轻量级草稿、暂停不重要的动画。但资源释放类操作应尽量放到onStop,因为onStop意味着Activity已经不再可见。尤其像相机、GPS、蓝牙扫描等高耗电模块,如果在onPause中释放,当用户仅仅切换窗口输入一行文字再切回来时,就需要重新初始化,造成明显延迟和电量浪费。
override fun onPause() {
super.onPause()
// 仅做轻量保存
draftManager.saveDraft()
}
override fun onStop() {
super.onStop()
// 完全不可见时再释放高耗能资源
locationManager.removeUpdates(this)
cameraManager.release()
}
状态恢复方面,不能完全依赖onRestoreInstanceState来恢复所有UI状态,因为多窗口尺寸调整不一定会导致销毁重建。如果窗口尺寸改变但Activity没有重建,系统会调用onConfigurationChanged,此时应该在该回调中重新计算布局约束,例如通过ViewGroup重新设置比例,而不是等待重建后从Bundle恢复。对复杂列表,建议使用ViewModel配合SavedStateHandle,在配置变更或多窗口切换时保留数据。
还应该关注Android的窗口焦点变化,尤其在使用自定义View或SurfaceView时,丢失焦点不意味着停止绘制。一个常见的误区是在onPause里停止所有绘制线程,导致分屏中看得见的画面被冻结。更合理的做法是监听onWindowFocusChanged,仅在窗口完全失焦且不可见时降低帧率或暂停绘制,这样可以在多窗口切换中保持流畅,同时不至于过度消耗资源。
Android多窗口模式Activity生命周期onMultiWindowModeChanged修改时间:2026-10-01 06:58:27