Android 横竖屏切换为什么会重建 Activity?configChanges 到底该不该用?
在 Android 开发中,经常会遇到这样一个问题:
为什么横竖屏切换时 Activity 会重新走生命周期?
为什么设置了android:configChanges="orientation"之后,有时候还是会重建?
既然重建这么麻烦,Android 为什么不默认不重建呢?
这篇文章就围绕横竖屏切换、Activity 重建、configChanges 的设计原因和实际使用场景做一次系统梳理。
一、横竖屏切换时,默认会发生什么?
默认情况下,如果 Activity 没有配置 android:configChanges:
<activity android:name=".MainActivity" />
当手机从竖屏切到横屏,或者从横屏切回竖屏时,系统会认为当前设备配置发生了变化。
于是 Activity 通常会经历一次销毁和重建:
onPause()
onStop()
onSaveInstanceState()
onDestroy()
onCreate()
onStart()
onRestoreInstanceState()
onResume()
也就是说,横竖屏切换不是简单地“重新绘制一下界面”,而是:
旧 Activity 被销毁
新的 Activity 被创建
新的资源重新加载
新的界面重新生成
这就是为什么很多初学者会发现:
横竖屏一切换,onCreate() 又执行了
这不是 bug,而是 Android 的默认设计。
二、Android 为什么默认要重建 Activity?
很多人第一反应是:
直接默认不重建不就好了吗?
横竖屏切换时让界面自己重新布局不行吗?
表面上看,这样好像更简单,但实际上 Android 默认重建 Activity 是有原因的。
1. 横屏和竖屏可能不是同一套界面
Android 的资源系统支持根据不同配置加载不同资源。
比如布局文件可以这样写:
res/layout/activity_main.xml
res/layout-land/activity_main.xml
竖屏时加载:
res/layout/activity_main.xml
横屏时加载:
res/layout-land/activity_main.xml
也就是说,同一个 Activity 在横屏和竖屏下,可能根本不是同一套布局。
例如一个新闻 App:
竖屏时:
上面是标题
中间是文章列表
点进去进入详情页
横屏时:
左边是文章列表
右边是文章详情
这已经不是“把原来的界面旋转 90 度”这么简单了,而是 UI 结构发生了变化。
如果系统不重建 Activity,就需要开发者自己处理:
重新选择横屏布局
重新加载横屏资源
重新计算 View 尺寸
重新调整控件显示隐藏
重新适配屏幕宽高
这对普通页面来说反而更容易出错。
所以 Android 默认采用了一种更稳妥的方式:
配置变化了
旧界面销毁
按照新配置重新创建界面
这样系统就可以自动加载最适合当前配置的资源。
2. 横竖屏切换不仅仅是 orientation 变化
很多人以为横竖屏切换只是:
orientation 变化
但实际上现代 Android 中,横竖屏切换通常还会伴随:
screenSize 变化
screenLayout 变化
可用窗口大小变化
在折叠屏、平板、多窗口模式、大屏设备上,这种情况更加明显。
所以只写:
android:configChanges="orientation"
在很多情况下是不够的。
因为你只告诉系统:
方向变化我自己处理
但你没有告诉系统:
屏幕尺寸变化我也自己处理
屏幕布局变化我也自己处理
于是当 screenSize 或 screenLayout 变化时,Activity 仍然可能被重建。
3. 配置变化不只是横竖屏
Android 的 configuration change 不只有横竖屏,还包括很多类型:
屏幕方向变化
屏幕尺寸变化
字体大小变化
系统语言变化
深色模式 / 浅色模式变化
键盘可用性变化
外接显示器变化
折叠屏展开或折叠
这些变化都会影响资源选择。
例如:
values-zh/
values-en/
values-night/
layout-land/
layout-sw600dp/
drawable-hdpi/
drawable-xhdpi/
如果系统不重建 Activity,那么这些资源是否重新加载、何时重新加载、怎么重新应用,就都需要开发者自己保证。
这显然不适合默认行为。
所以 Android 的设计思路是:
默认让系统处理配置变化
开发者只负责保存和恢复状态
而不是:
默认让开发者手动处理所有配置变化
三、configChanges 的本质是什么?
android:configChanges 的本质是告诉系统:
某些配置变化不要帮我重建 Activity,我自己处理。
例如:
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize|screenLayout" />
这表示:
orientation 变化,我自己处理
screenSize 变化,我自己处理
screenLayout 变化,我自己处理
配置之后,当这些配置发生变化时,Activity 不会因为这些变化销毁重建,而是回调:
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
// 在这里自己处理配置变化
}
所以可以这样理解:
注意,这里有一个重点:
configChanges只对你声明的配置项生效。
如果你只写了:
android:configChanges="orientation"
那么只有 orientation 被你接管。
如果同时发生了:
screenSize 变化
screenLayout 变化
keyboardHidden 变化
而你没有声明这些变化,那么 Activity 仍然可能被系统重建。
四、为什么以前资料说切竖屏会执行两次?
有些老资料会说:
不设置 configChanges 时,切横屏执行一次,切竖屏执行两次
只设置 orientation 时,切横屏执行一次,切竖屏执行两次
这个说法在某些老版本、老设备、模拟器环境下确实可能出现,但不能简单理解成“所有 Android 设备都是这样”。
更准确的理解是:
横竖屏切换过程中,可能连续触发了多个 configuration change。
例如:
orientation 变化
screenSize 变化
keyboardHidden 变化
如果这些配置变化不是一次性合并处理,或者某些配置项没有被声明处理,就可能导致 Activity 重建多次。
这里经常被提到的 keyboardHidden,主要指的是:
硬件键盘可见性变化
外接键盘状态变化
可拆卸键盘状态变化
它不是普通输入框弹出的软键盘。
普通软键盘弹出,一般影响的是:
窗口可见区域
WindowInsets
adjustResize
adjustPan
布局重新测量
通常不会导致 Activity 走:
onDestroy()
onCreate()
所以,“切竖屏执行两次是因为键盘问题”这个说法要谨慎理解。
更准确地说:
可能和 keyboardHidden 有关
但本质是一次旋转过程中触发了多个配置变化
现代 Android 中,更常见需要关注的是:
orientation
screenSize
screenLayout
五、横竖屏不想重建,应该怎么配置?
如果确实希望横竖屏切换时 Activity 不重建,常见配置是:
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize|screenLayout" />
如果你的页面还涉及外接键盘、硬件键盘、可拆卸键盘,可以再加:
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize|screenLayout|keyboard|keyboardHidden" />
然后在 Activity 中重写:
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
// 横屏处理
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
// 竖屏处理
}
}
但是要注意:
配置了
configChanges,不是说系统什么都不管了,而是 Activity 不会重建,开发者需要自己处理业务上的配置变化。
View 本身仍然可能重新测量、重新布局、重新绘制。
你真正需要处理的是:
横屏显示哪些控件
竖屏显示哪些控件
播放器是否全屏
地图面板放左边还是放下面
相机预览方向是否正确
游戏画布尺寸是否变化
WebView 是否需要保持状态
六、自己处理 configChanges 的实际场景
普通页面一般不建议随便配置 configChanges。
真正适合自己处理配置变化的,通常是那些:
重建成本高
状态恢复复杂
用户体验容易被打断
底层资源初始化昂贵
的页面。
下面看几个典型场景。
1. 视频播放页
视频播放页是最典型的场景。
竖屏时可能是:
上面播放视频
下面显示标题、简介、评论
横屏时可能是:
视频全屏播放
隐藏标题、评论、状态栏、导航栏
如果横竖屏切换时 Activity 重建,可能导致:
播放器重新初始化
播放进度丢失
画面黑屏
重新缓冲
用户体验变差
所以视频页常常会配置:
<activity
android:name=".VideoActivity"
android:configChanges="orientation|screenSize|screenLayout" />
然后自己处理横竖屏 UI:
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
enterLandscapeMode()
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
enterPortraitMode()
}
}
private fun enterLandscapeMode() {
titleView.visibility = View.GONE
commentRecyclerView.visibility = View.GONE
val params = playerView.layoutParams
params.width = ViewGroup.LayoutParams.MATCH_PARENT
params.height = ViewGroup.LayoutParams.MATCH_PARENT
playerView.layoutParams = params
}
private fun enterPortraitMode() {
titleView.visibility = View.VISIBLE
commentRecyclerView.visibility = View.VISIBLE
val screenWidth = resources.displayMetrics.widthPixels
val params = playerView.layoutParams
params.width = ViewGroup.LayoutParams.MATCH_PARENT
params.height = screenWidth * 9 / 16
playerView.layoutParams = params
}
这个场景的核心是:
不销毁播放器
只调整播放器容器和周围 UI
2. 相机 / 扫码页面
相机页面、扫码页面、录像页面也经常自己处理配置变化。
如果 Activity 被重建,可能导致:
Camera 关闭再打开
预览黑屏
扫码中断
录像中断
重新对焦
所以可以配置:
<activity
android:name=".CameraActivity"
android:configChanges="orientation|screenSize|screenLayout" />
然后在配置变化时重新计算预览方向和布局:
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
updateCameraPreview()
}
private fun updateCameraPreview() {
val rotation = windowManager.defaultDisplay.rotation
when (rotation) {
Surface.ROTATION_0 -> {
previewView.rotation = 0f
}
Surface.ROTATION_90 -> {
previewView.rotation = 90f
}
Surface.ROTATION_270 -> {
previewView.rotation = 270f
}
}
}
实际项目中,相机页面还可能需要处理:
预览尺寸
传感器方向
裁剪区域
取景框位置
扫码框位置
核心思想是:
不重启相机
只调整预览方向和界面布局
3. WebView 页面
WebView 页面也经常不希望 Activity 因横竖屏重建。
例如用户正在填写一个 H5 表单,如果一旋转屏幕 Activity 重建,WebView 可能重新加载,导致用户输入丢失。
可以这样配置:
<activity
android:name=".WebActivity"
android:configChanges="orientation|screenSize|screenLayout|keyboard|keyboardHidden" />
然后:
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
// 很多 WebView 页面这里不需要做太多事情
// 主要目的是避免 WebView 被销毁和重新加载
}
这个场景的重点不是复杂处理横竖屏布局,而是:
保持 WebView 实例
避免页面刷新
避免表单状态丢失
4. 地图页面
地图页面同样适合自己处理配置变化。
如果横竖屏切换导致 Activity 重建,可能出现:
地图重新初始化
定位状态丢失
缩放级别变化
Marker 重新加载
路线规划重新绘制
可以这样配置:
<activity
android:name=".MapActivity"
android:configChanges="orientation|screenSize|screenLayout" />
然后根据方向调整 UI:
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
sidePanel.visibility = View.VISIBLE
bottomPanel.visibility = View.GONE
} else {
sidePanel.visibility = View.GONE
bottomPanel.visibility = View.VISIBLE
}
mapView.requestLayout()
}
横屏时:
左边地图,右边路线详情
竖屏时:
上面地图,下面路线详情
Activity 不重建,地图对象也不需要重新初始化。
5. 游戏页面
游戏页面通常使用:
SurfaceView
GLSurfaceView
OpenGL ES
Unity
Cocos
自研渲染引擎
这类页面如果 Activity 重建,可能导致:
游戏暂停
场景重新加载
纹理资源重新创建
音频中断
网络状态异常
所以游戏页面经常会配置:
<activity
android:name=".GameActivity"
android:configChanges="orientation|screenSize|screenLayout|keyboard|keyboardHidden" />
然后通知游戏引擎窗口大小变了:
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
val width = resources.displayMetrics.widthPixels
val height = resources.displayMetrics.heightPixels
gameView.onScreenSizeChanged(width, height)
}
这个场景中,Activity 不重建,游戏引擎继续运行,只是更新渲染区域和输入区域。
七、普通页面为什么不建议滥用 configChanges?
既然 configChanges 可以避免 Activity 重建,那是不是所有页面都应该加?
答案是不建议。
比如这些页面:
登录页
设置页
列表页
详情页
普通表单页
普通业务页
通常没有必要自己接管配置变化。
因为默认重建虽然看起来麻烦,但它有一个重要好处:
系统会自动加载当前配置下最合适的资源
比如:
layout-land
layout-sw600dp
values-night
values-zh
drawable-xhdpi
如果你用了 configChanges,这些变化就不会通过 Activity 重建自动完成。
你需要自己考虑:
横屏布局是否要切换
夜间模式资源是否要刷新
语言变化是否要重新应用
字体大小变化是否要重新适配
屏幕尺寸变化是否要重新计算
如果处理不完整,就可能出现:
界面布局错乱
资源没有更新
语言切换不生效
字体大小不适配
深色模式显示异常
所以 configChanges 不是性能优化万能药。
它更像是一种声明:
系统,这些配置变化我自己负责,你不要重建我。
如果你没有真的处理这些变化,就不要轻易声明。
八、configChanges 和 onSaveInstanceState 的关系
默认重建 Activity 时,开发者通常需要通过状态保存机制恢复数据。
例如:
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString("keyword", searchEditText.text.toString())
outState.putInt("scrollY", recyclerView.computeVerticalScrollOffset())
}
在重新创建时恢复:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val keyword = savedInstanceState?.getString("keyword")
}
如果使用 ViewModel,也可以在配置变化中保留页面数据。
也就是说,普通页面更推荐:
让系统重建 Activity
用 ViewModel 保存业务状态
用 onSaveInstanceState 保存临时 UI 状态
而不是为了避免重建,盲目使用 configChanges。
九、面试中怎么回答?
如果面试官问:
Android 横竖屏切换为什么 Activity 会重建?
可以这样回答:
Android 把横竖屏切换看作一种配置变化。配置变化会影响资源选择,比如 layout-land、不同屏幕尺寸资源、不同语言资源、深色模式资源等。为了保证 Activity 使用的是当前配置下最合适的资源,系统默认会销毁并重建 Activity。
如果不希望系统重建,可以在 Manifest 中配置 android:configChanges,声明哪些配置变化由开发者自己处理。这样对应配置变化发生时,Activity 不会重建,而是回调 onConfigurationChanged()。但这意味着开发者需要自己处理布局、资源、尺寸、方向等变化。
如果继续追问:
为什么只写
orientation有时还会重建?
可以这样回答:
因为现代 Android 中横竖屏切换不只是 orientation 变化,还可能伴随 screenSize、screenLayout 等变化。只声明 orientation 只能接管方向变化,其他未声明的配置变化仍然可能导致 Activity 重建。所以常见写法是:
android:configChanges="orientation|screenSize|screenLayout"
如果涉及硬件键盘或外接键盘,还可以加上:
keyboard|keyboardHidden
如果继续追问:
为什么不所有 Activity 都配置 configChanges?
可以这样回答:
因为配置 configChanges 后,系统不会自动通过 Activity 重建来重新加载适配资源,开发者需要自己处理配置变化。如果处理不完整,容易导致布局、资源、语言、字体、深色模式等状态不一致。普通页面让系统重建更安全,只有视频、相机、WebView、地图、游戏等重建成本高、状态恢复复杂的页面才适合自己处理。
十、总结
Android 横竖屏切换默认重建 Activity,不是设计缺陷,而是为了保证资源和界面状态与当前设备配置一致。
可以用一句话总结:
默认重建,是系统帮你处理配置变化;
配置 configChanges,是开发者接管配置变化。
普通页面建议让系统处理:
配置变化 -> Activity 重建 -> 自动加载新资源
特殊页面可以自己处理:
配置变化 -> onConfigurationChanged() -> 手动调整 UI 和业务状态
实际开发中,configChanges 适合这些场景:
最后记住一点:
configChanges不是为了偷懒,而是为了在特殊场景下接管系统默认行为。
如果你声明自己处理配置变化,就真的要把这些变化处理完整。
Android 横竖屏切换为什么会重建 Activity?configChanges 到底该不该用?
https://lautung.com/archives/UVsCzVIt