Activity及Fragment的数据保存和恢复
Activity 及 Fragment 的数据保存和恢复
Android 数据保存和恢复
模拟 App 被系统 kill(requires emulator running P+)
- 查看要模拟的 App 的进程,看是否存活
1
adb shell ps -A |grep me.hacket
- 按 Home 键回到桌面(不按 Home kill 不了)
- 运行命令
1
adb shell am kill me.hacket.assistant
- 再次确认查看是否存活,现在应该为空
1
adb shell ps -A |grep me.hacket
- 再次打开该 App,就能模拟 App 被系统 kill 的操作
Activity 重建
在 Android 系统中,需要数据恢复有如下两种场景:
配置变更
资源相关的配置发生改变导致 Activity 被杀死并重新创建。(如屏幕旋转)
- 横竖屏切换
- 键盘可用性/显示隐藏更改
- 多窗口设定
- 切换系统语言
- 切换深色模式
- 字体大小切换
- 系统导航方式变更
Activity 重建的细节
Activity
重建过程中,先将原来的Activity
进行销毁(从onPause
>onStop
>onDestroy
>onCreate
的生命周期)。- 虽然是不同的 Activity 对象,但重建时使用的
ActivityClientRecord
却是相同的,而ActivityClientRecord
最终是被ActivityThread
持有,它是全局的。这也是onSaveInstanceState/onRestoreInstanceState
能够存储与恢复数据的本质原因。
Activity 重建的原因分析
- 系统内存不足
- 配置项改变(configuration change )
例如横竖屏切换,白天黑夜模式切换 - 调用 activity 的 onCreate() 方法
配置项声明不重建 activity
- 横竖屏切换
1
<activity android:name="" android:configChanges="orientation|screenSize"></activity>
- 白天/黑夜模式
1
<activity android:name="" android:configChanges="uiMode"></activity>
- 系统语言切换
1
<activity android:name="" android:configChanges="locale|layoutDirection|keyboard"></activity>
内存不足
资源内存不足导致低优先级的 Activity 被杀死。(开发者选项中不保留活动可以模拟)
突发情况
- 点击 back 后退键
- 点击锁屏键
- 点击 home 键
- 其他 app 进入前台 (如电话来了)
- 启动了另外一个 activity
- 屏幕方向旋转
- app 被 kill 掉
除了按 back 键,其他情况下,
onSaveInstanceState
方法都会调用; 而且onPause
方法所有情况下都是会调用的。
几种数据恢复方式的总结
- ViewModel 的局限,销毁重建的方案 SavedStateHandlehttps://juejin.im/post/5e2c6914f265da3e377eff25
https://github.com/husaynhakeem/Androidx-SavedState-Playground
ViewModel 做数据保存恢复(官方推荐) ViewModelSavedState
见 ViewModel 章节介绍
1
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.2.0"
不需要手动引入该库,因为 fragment 库已经在内部引入该库
- ViewModel 默认只能解决配置更改数据恢复问题,资源内存不足导致低优先级的 Activity 被杀死数据不能恢复
- https://www.jianshu.com/p/9772b88e3c1e
【背上 Jetpack】绝不丢失的状态 androidx SaveState ViewModel-SaveState 分析
androidx SaveState
在 androidx
下,提供了 SavedState
库帮助 activity 和 fragment 处理状态保存和恢复
1
implementation "androidx.savedstate:savedstate:1.0.0"
这是一个很小的库,不需要显示地声明,因为 activity 库内部已经引入了
 |
SavedStateRegistryOwner
持有 SavedStateRegistry
的组件。 默认情况下,androidx
包中的 ComponentActivity 和 Fragment 都实现此接口
1
2
3
4
public interface SavedStateRegistryOwner extends LifecycleOwner {
@NonNull
SavedStateRegistry getSavedStateRegistry();
}
SavedStateRegistryController
一个包装 SavedStateRegistry
并允许通过其 2 个主要方法对其进行控制的组件:performRestore(savedState)
和 performSave(outBundle)
。这两个方法将内部通过 SavedStateRegistry
中的方法处理。
1
2
3
4
5
6
7
8
9
10
11
12
13
public final class SavedStateRegistryController {
private final SavedStateRegistryOwner mOwner;
private final SavedStateRegistry mRegistry;
public void performRestore(@Nullable Bundle savedState) {
// ...
mRegistry.performRestore(lifecycle, savedState);
}
public void performSave(@NonNull Bundle outBundle) {
mRegistry.performSave(outBundle);
}
}
SavedStateProvider
保存状态的组件,此状态将在以后恢复并使用
1
2
3
4
public interface SavedStateProvider {
@NonNull
Bundle saveState();
}
SavedStateRegistry
管理 SavedStateProvider 列表的组件,此注册表绑定了其所有者的生命周期(即 activity 或 fragment)。每次创建生命周期所有者都会创建一个新的实例
在调用 activity 的 onCreate(savedInstanceState)
方法之后,将调用其 performRestore(state)
方法,以恢复系统杀死其所有者之前保存的任何状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// androidx.activity.ComponentActivity
protected void onSaveInstanceState(@NonNull Bundle outState) {
Lifecycle lifecycle = getLifecycle();
if (lifecycle instanceof LifecycleRegistry) {
((LifecycleRegistry) lifecycle).setCurrentState(Lifecycle.State.CREATED);
}
super.onSaveInstanceState(outState);
mSavedStateRegistryController.performSave(outState);
}
// SavedStateRegistryController
public void performSave(@NonNull Bundle outBundle) {
mRegistry.performSave(outBundle);
}
// SavedStateRegistry
void performSave(@NonNull Bundle outBundle) {
Bundle components = new Bundle();
if (mRestoredState != null) {
components.putAll(mRestoredState);
}
for (Iterator<Map.Entry<String, SavedStateProvider>> it =
mComponents.iteratorWithAdditions(); it.hasNext(); ) {
Map.Entry<String, SavedStateProvider> entry1 = it.next();
components.putBundle(entry1.getKey(), entry1.getValue().saveState());
}
outBundle.putBundle(SAVED_COMPONENTS_KEY, components);
}
Activity 数据的保存和恢复
onSaveInstanceState 与 onRestoreInstanceState – 临时数据保存
- 何时调用? 在 activity 变得 “ 容易 “ 被系统销毁时,该 activity 的
onSaveInstanceState
方法会被调用执行,也就是说是被动的调用,而不是用户主动按 back 键。 - 何地调用?
onSaveInstanceState
的调用是处于onPause
和onStop
之间的; 但可以保证,onSaveInstanceState
方法会在onStop
之前调用,但是是否在onPause
之前就不一定了。 - 设计的目的 对一些临时的、非永久性的数据存储并恢复。如:EditText 输入的内容、CheckBox 是否勾选、ScrollView 滚动的位置,目前视频播放的位置等
- 保存
1
2
3
4
5
6
7
8
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
String data = etData.getText().toString();
outState.putString("data", data);
LogUtil.i(TAG, getClass().getSimpleName() + ":onSaveInstanceState...");
}
- 恢复 在 onCreate 或者 onRestoreInstanceState 方法中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
// ...
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
LogUtil.i(TAG, getClass().getSimpleName() + ":onRestoreInstanceState...");
if (savedInstanceState != null) {
String data = savedInstanceState.getString("data");
etData.setText(data);
}
}
注意点: 在 Android 本身中,View 自己实现了 onSaveInstanceState
方法,这些控件自己就具有保存临时数据和恢复临时数据的能力,值得一提的是,只有当你给这个 wiget 在 xml 中指定id时,它才具有保存数据并且恢复的能力,并且不同的 wiget 还不能共用这个 id,否则会出现数据覆盖的情况。EditText 默认都有数据保存恢复,而 TextView 没有,需要加上属性 android:freezesText="true"
下面这些控件都实现了:
 |
使用 onSaveInstanceState 与 onRestoreInstanceState 方法,能处理场景 1 与场景 2 的情况。当你的界面数据简单且轻量时,例如原始数据类型或简单对象(比如 String),则我们可以采用该方式。如果你需要恢复的数据较为复杂,那你应该考虑使用 ViewModle + onSaveInstanceState()
(为什么要配合使用,会在下文进行讲解),因为使用 onSaveInstanceState() 会导致序列化或反序列化,而这,有一定的时间消耗。
onRetainNonConfigurationInstance 与 getLastNonConfigurationInstance(过期不推荐,推荐 ViewModel)
在 Activity 中提供了 onRetainNonConfigurationInstance
方法,用于处理配置发生改变时数据的保存。随后在重新创建的 Activity 中调用 getLastNonConfigurationInstance
获取上次保存的数据。我们不能直接重写上述方法,如果想在 Activity 中自定义想要恢复的数据,需要我们调用上述两个方法的内部方法:
- onRetainCustomNonConfigurationInstance()
- getLastCustomNonConfigurationInstance()
注意:onRetainNonConfigurationInstance 方法系统调用时机介于 onStop - onDestroy 之间,getLastNonConfigurationInstance 方法可在 onCreate 与 onStart 方法中调用。在 Android 3.0 后,官方推荐使用 Fragment#setRetainInstance(true) 的方式进行数据的恢复。之所以推荐这种方式,个人猜测是为了降低 Activity 的冗余,将数据恢复的任务从 Activity 抽离出来,这更符合单一职责的设计模式。
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String name = (String) getLastCustomNonConfigurationInstance();
if (!TextUtils.isEmpty(name)) {
//获取恢复后的数据,执行相应操作
}
}
//你可以可以在onStart中,获取恢复的数据
// @Override
// protected void onStart() {
// super.onStart();
// String name = (String) getLastCustomNonConfigurationInstance();
// if (!TextUtils.isEmpty(name)) {
// }
// }
@Nullable
@Override
public Object onRetainCustomNonConfigurationInstance() {
return "AndyJennifer";
}
}
onPause – 持久数据保存
程序突然死亡了,能保证的就是 onPause
方法是一定会调用的,而 onStop 和 onDestory 方法并不一定,所以这个特性使得 onPause 是持久化相关数据的最后的可靠时机。当然 onPause 方法不能做大量的操作,这会影响下一个 Activity 入栈。
小结
临时数据使用 onSaveInstanceState 保存恢复,永久性数据使用 onPause 方法保存。
测试源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
public class OnSaveInstanceStateActivity extends BaseActivity {
private static final String TAG = "OnSaveInstanceStateActivity";
private EditText etData;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_on_save_instance);
etData = (EditText) findViewById(R.id.et_data);
LogUtil.i(TAG, getClass().getSimpleName() + ":onCreate...savedInstanceState:" + (savedInstanceState == null));
}
@Override
public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
super.onCreate(savedInstanceState, persistentState);
LogUtil.i(TAG,
getClass().getSimpleName() + ":onCreate..persistentState...savedInstanceState:" + (savedInstanceState
== null));
}
@Override
public void onRestoreInstanceState(Bundle savedInstanceState, PersistableBundle persistentState) {
super.onRestoreInstanceState(savedInstanceState, persistentState);
LogUtil.i(TAG, getClass().getSimpleName() + ":onRestoreInstanceState...persistentState");
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
LogUtil.i(TAG, getClass().getSimpleName() + ":onRestoreInstanceState...");
if (savedInstanceState != null) {
String data = savedInstanceState.getString("data");
etData.setText(data);
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
String data = etData.getText().toString();
outState.putString("data", data);
LogUtil.i(TAG, getClass().getSimpleName() + ":onSaveInstanceState...");
}
@Override
protected void onStart() {
super.onStart();
LogUtil.i(TAG, getClass().getSimpleName() + ":onStart...");
}
@Override
protected void onResume() {
super.onResume();
LogUtil.i(TAG, getClass().getSimpleName() + ":onResume...");
}
@Override
protected void onPause() {
super.onPause();
LogUtil.i(TAG, getClass().getSimpleName() + ":onPause...");
}
@Override
protected void onStop() {
super.onStop();
LogUtil.i(TAG, getClass().getSimpleName() + ":onStop...");
}
@Override
protected void onDestroy() {
super.onDestroy();
LogUtil.i(TAG, getClass().getSimpleName() + ":onDestroy...");
}
@Override
protected void onRestart() {
super.onRestart();
LogUtil.i(TAG, getClass().getSimpleName() + ":onRestart...");
}
}
Android 中 Fragment 数据保存和恢复
返回栈
类比 Activity,Fragment 返回栈其实是保存 Fragment 的栈结构。区别在于:Fragment 的返回栈由 Activity 管理;而 Activity 的返回栈由系统管理。
单个 Fragment
和 Activity 类似
当一个 fragment 孤零零地呆在返回栈时,它所处的情况与 Activity 如出一辙。类比 Activity 对数据的保存和恢复,我们可以对此得出结论:
- 临时数据 对于临时数据,我们使用onSaveInstanceState方法进行保存,并且在onCreateView方法中恢复(请注意是 onCreateView)。
- 永久数据 对于持久性数据,我们要在 onPause 方法中进行存储。
多个 Fragment
- 1、add/hide/show – 视图和实例都不会被销毁
多个 Fragment 进行切换的时候,采用的是 add/hide/show 方式,那么该 Fragment 的视图和实例都不会被销毁 (即 onDestroyView 和 onDetach 都不会被调用),如下图,Fragment1 切换到 Fragment2 的时候,只调用了 Fragment1 的 onResume(),onDestroyView 和 onDetach 都不会被调用。
- 2、replace、remove – 视图会被销毁,实例不会被销毁
如果存在回退栈情况下,Fragment 任务栈中有多个 Fragment 时,进入到下一个 Fragment 时,并不会销毁 Fragment 实例,而仅仅是销毁 Fragment 的视图 (即最终调用的是onDestroyView
方法)
下面是采用 replace 加入到会退栈情况:
按 back 键,Fragment2 出栈并销毁实例,此时 Fragment1 还在栈中,然后创建 view 视图。
保存临时数据,并不能仅保存在 onSaveInstanceState 中(因为它可能不会调用),还应该在 onDestoryView 方法中进行保存临时数据的操作
异常情况
当某个 Activity 出现异常,导致 Activity 重新启动,如果将要保持 Fragment 状态的 onSaveInstanceState
方法注释掉,这样就不会去保存 Fragment 的状态,当主 Activity 重启的时候,也就不会去恢复之前遗留的 Fragment。
1
2
3
4
@Override
protected void onSaveInstanceState(Bundle outState) {
// super.onSaveInstanceState(outState);
}
Ref
- Android 中 Activity 数据的保存和恢复: http://www.jianshu.com/p/6622434511f7