android/java中,配置更改导致activity销毁重建的解决方法
什么是配置更改
配置更改(Configuration Change) 是指设备状态发生改变,从而可能影响应用资源的获取和当前UI的呈现。
当配置更改发生时,Android系统默认的行为是销毁并重新创建正在前台运行的Activity(以及其中的Fragment)。这样做的目的是为了让应用能够自动加载与新配置相匹配的替代资源(例如横屏布局layout-land
、不同语言的字符串等)。
什么时候会触发配置更改?
配置更改由一系列设备状态的改变触发,以下是最常见的类型:
1. 屏幕相关(最常见)
- 屏幕方向(Orientation):手机在竖屏(portrait)和横屏(landscape)之间旋转。
- 屏幕尺寸(Screen Size):当应用在分屏/多窗口模式下运行时调整大小。(API 24+)
- 屏幕布局(Screen Layout):通常是因为激活了不同的显示模式或外接了显示器。
2. 区域和语言
- 语言环境(Locale):用户更改了系统的语言或区域设置。
- 字体大小(Font Scale):用户在系统设置中调整了默认的字体大小。
3. 输入设备
- 键盘可用性(Keyboard Availability):物理键盘被滑出或接入(例如,连接蓝牙键盘)。
4. 其他系统设置
- 字体粗细(Font Weight):调整了系统字体的粗细。
- UI模式(UI Mode):设备进入了夜间模式、汽车模式或电视模式。
- 布局方向(Layout Direction):从从左到右(LTR)的布局(如英语)切换到从右到左(RTL)的布局(如阿拉伯语)。
配置更改的默认处理流程
1. Activity 的生命周期变化
默认情况下,旋转屏幕会经历以下完整生命周期:
旧 Activity 流程:
onPause()
-> onStop()
-> onDestroy()
新 Activity 流程:
onCreate()
-> onStart()
-> onResume()
关键点:
- 全新的实例:重新创建的 Activity 是一个全新的对象,旧实例会被垃圾回收。
- 所有成员变量丢失:由于对象是全新的,你在类中定义的任何非静态的成员变量(如
String mData;
,List<User> mUserList;
)都会丢失,恢复为默认值(如null
)。 - UI 状态:Android 系统会尝试自动恢复 UI 状态,例如
EditText
中的文本。这是因为系统在销毁前会调用onSaveInstanceState(Bundle)
,并在重新创建时通过onCreate(Bundle savedInstanceState)
将保存的数据包传递回来。
2. Fragment 的生命周期变化
Fragment 的生命周期与其宿主 Activity 紧密绑定。
旧 Fragment 流程:
onPause()
-> onStop()
-> onDestroyView()
-> onDestroy()
-> onDetach()
新 Fragment 流程:
onAttach()
-> onCreate()
-> onCreateView()
-> onViewCreated()
-> onStart()
-> onResume()
关键点:
- 实例可能被保留,也可能不被保留:这取决于你如何创建 Fragment。
- 使用
XML
布局或FragmentTransaction#add
:Fragment 会随着 Activity 一起被完全销毁并重新创建。所有成员变量同样会丢失。 - 使用
FragmentTransaction#add
并设置setRetainInstance(true)
(已弃用):Fragment 实例本身不会被销毁(跳过onDestroy
和onCreate
),但它会从旧 Activity 中分离 (onDetach
),并附着到新 Activity 上 (onAttach
)。注意:此方法现已弃用,不推荐使用。
- 使用
- 视图重建:即使 Fragment 实例被保留(通过已弃用的方法),它的视图层次结构(
onCreateView
返回的 View)也一定会被销毁和重建。因此,在onDestroyView()
中清理所有对视图的引用至关重要,否则会发生内存泄漏。 - 数据保存:和 Activity 一样,Fragment 也有
onSaveInstanceState(Bundle)
回调,用于在视图被销毁前保存临时数据。
3. 数据的命运总结
数据存储位置 | 屏幕旋转后是否保留? | 原因和说明 |
---|---|---|
Activity 的成员变量 | 否 | Activity 实例被完全销毁,新实例是全新的对象。 |
Fragment 的成员变量 | 通常否 | 默认情况下 Fragment 随 Activity 一起销毁。仅在使用已弃用的 setRetainInstance(true) 时保留。 |
onSaveInstanceState(Bundle) 保存的数据 | 是 | 系统自动调用此方法保存数据(如 UI 状态),并在 onCreate 或 onRestoreInstanceState 中恢复。注意:Bundle 不适合存放大对象(如 Bitmap)或复杂数据结构。 |
ViewModel 中的数据 | 是 | 这是官方推荐的解决方案! ViewModel 的生命周期范围是 View(UI),它会在配置变更后依然存活,并在新的 Activity/Fragment 中提供同一个实例的数据。 |
持久化存储的数据 (数据库, SharedPreferences, 文件) | 是 | 数据被物理存储在设备上,与 Activity/Fragment 的生命周期无关。 |
如何手动处理配置更改?(覆盖默认行为)
有时,默认的销毁重建行为代价高昂(例如,需要保持网络连接、播放视频不能中断),或者你想自己处理UI调整。这时你有两种选择:
方案一:在 AndroidManifest.xml
中声明(不推荐用于大部分情况)
你可以在<activity>
标签中指定你的Activity将自行处理哪些配置变更。系统将不会重启Activity,而是会调用它的 onConfigurationChanged()
方法。
<activityandroid:name=".MyActivity"android:configChanges="orientation|screenSize|screenLayout" />
常见值:
orientation
:屏幕方向(旧API)screenSize
:当前可用屏幕尺寸(API 13+,必须与orientation
一起使用)screenLayout
:屏幕布局(可能因折叠屏等改变)keyboardHidden
:键盘可用性改变locale
:语言环境改变
注意事项:
- 你必须手动处理所有变化:在
onConfigurationChanged()
中,你需要重新加载资源、调整UI布局。如果你声明了orientation
但没处理,横屏时界面可能会错乱。 - 容易引入错误:忘记处理某些UI更新是常见错误。
- 适用于特定场景:通常用于游戏、视频播放器等不希望中断的界面。
@Override
public void onConfigurationChanged(Configuration newConfig) {super.onConfigurationChanged(newConfig);// 检查是否是屏幕方向变化if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {// 手动进行一些横屏下的UI调整,例如隐藏某些视图Log.d("ConfigChange", "Now in Landscape");} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {// 手动进行一些竖屏下的UI调整Log.d("ConfigChange", "Now in Portrait");}
}
方案二:使用 ViewModel
+ onSaveInstanceState
(官方推荐的最佳实践)
这是现代Android架构的首选方案。你不需要阻止Activity重启,而是将数据与UI控制器(Activity/Fragment) 的生命周期分离开。
-
ViewModel:用于持有和管理UI相关的数据。它的生命周期比Activity更长,在配置更改期间不会被销毁。因此,Activity重建后可以访问到同一个ViewModel实例,数据得以保留。
- 存什么:用户列表、表单数据、网络请求结果等。
-
onSaveInstanceState:用于保存少量的、序列化的UI状态,以便在系统杀死应用进程后(而不仅仅是配置更改)还能恢复。
- 存什么:简单的ID、滚动位置、EditText中的临时文本等。
总结对比:
特性 | 默认行为(销毁重建) | 手动处理 (configChanges ) | 使用 ViewModel |
---|---|---|---|
Activity生命周期 | 销毁并新建 | 不重启,只调用onConfigurationChanged | 销毁并新建 |
数据如何处理 | 丢失(需通过Bundle保存) | 自行维护,数据保留在Activity中 | 自动保留在ViewModel中 |
复杂性 | 低(系统自动) | 高(需手动处理所有UI更新) | 中(架构清晰) |
推荐度 | - | 低(特定场景使用) | 高(官方首选方案) |
结论:对于绝大多数应用,不要使用android:configChanges
,而是拥抱系统的默认行为,并采用 ViewModel
来优雅地解决配置更改时的数据持久化问题。 Activity 和其中的 Fragment 被销毁并重新创建。**
总结
概念 | 默认行为 | 推荐解决方案 |
---|---|---|
Activity | 销毁并重建,数据丢失。 | 使用 ViewModel 持有数据,使用 onSaveInstanceState 保存轻量级 UI 状态。 |
Fragment | 随 Activity 销毁并重建,数据丢失。 | 同上。将核心数据放在 ViewModel 中,Fragment 只负责 UI 逻辑。 |
核心思想: 将数据(What to show)与UI 控制器(Activity/Fragment,How to show)分离。ViewModel
是实现这种分离的最佳工具,它能优雅地应对配置变更(如屏幕旋转)导致的数据丢失问题。