State save and restore of Fragment in Android

随着Android 4.0+ SDK的推广,Fragment也得到了越来越广泛的使用,我个人也越来越喜欢用Fragment来实现各种页面片段。在一个Android工程中,往往很多Activity可以使用同一个Base Activity的框架,这样每个页面使用各自的Fragment就足够了,并且不同页面的Fragment往往可以达到重用的效果,这是之前用Activity时不太容易实现的。

最近在做开发的过程中遇到了Fragment由于内存回收被销毁而无法正常恢复到之前状态的问题。实际上,Android SDK提供了Activity和Fragment恢复状态的机制,因此特别整理一下。

基本作用

Activity提供了onSaveInstanceState()和onRestoreInstanceState()方法,用来实现数据保存和恢复的功能。Fragment中只有onSaveInstanceState()方法,其数据恢复可以在onActivityCreated()方法中实现。

onSaveInstanceState()和onRestoreInstanceState()并不是生命周期方法,并不一定会被触发。当应用遇到意外情况(如内存不足、用户直接按Home键)由系统销毁一个Activity时,onSaveInstanceState()会被调用。但是当用户主动去销毁一个Activity时(例如按返回键),onSaveInstanceState()方法就不会调用。

为了保证Activity(或Fragment)能够正常通过onRestoreInstanceState(Bundle)或者onActivityCreated(Bundle)方法恢复,我们需要在Activity(或Fragment)被杀掉之前保存每个实例的状态。

实例

在我开发的APP中,有一个页面用来展示用户的详细信息和用户发布的内容,该页面通过一个复用的Fragment来实现,其中包含了一个User的实例作为成员变量,来表示一个用户的信息。User类继承了Seriable接口,可以作为可序列化的对象来进行传递。

Fragment的一个简单说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class UserDetailFragment extends ... implements ... {
// 代表用户信息的一个实例
private User mUser;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(Bundle savedInstanceState);
// 使用User的对象做一些操作
// ...
mUser.getUserData();
// ...
}
/**
* 设置用户的ID,初始化Fragment时调用(调用时间在onActivityCreated之前)
*/
public void initParams(int uid) {
mUser = new User();
mUser.setUid(uid);
}
}

当我从该页面做了一些操作,跳转到另外的页面时,由于内存原因,该Fragment被系统回收了,这样就导致我回到该页面时,onActivityCreated()方法又被调用,Fragment被重建,但是User对象没有进行初始化,从而导致了NullPointerException,程序crash。

解决方案

研究了onSaveInstanceState()方法的用法,我发现应该在Fragment被回收之前,保存User对象的状态。该保存状态的操作是在onSavedInstance()方法中实现的:

1
2
3
4
5
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable("user", mUser);
}

outState参数是一个Bundle对象,可以用来暂时保存页面上的关键数据。当Fragment被系统回收时,onSaveInstanceState()方法被调用,我们把User对象暂存在outState中。当页面回收时,outState对象将会作为参数传入onActivityCreated(Bundle)方法中,这时只要在onActivityCreated()方法中恢复User对象即可。

1
2
3
4
// 恢复User对象的实现
if(savedInstanceState != null) {
mUser = (User) savedInstanceState.getSerializable("user");
}

因此,完善后的代码如下:

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
public class UserDetailFragment extends ... implements ... {
// 代表用户信息的一个实例
private User mUser;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(Bundle savedInstanceState);
// 从Bundle对象中恢复User对象
if(savedInstanceState != null) {
mUser = (User) savedInstanceState.getSerializable("user");
}
// 使用User的对象做一些操作
// ...
mUser.getUserData();
// ...
}
/**
* 设置用户的ID,初始化Fragment时调用(调用时间在onActivityCreated之前)
*/
public void initParams(int uid) {
mUser = new User();
mUser.setUid(uid);
}
/**
* 页面被回收时,保存User对象的状态
*/
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSerializable("user", mUser);
}
}

一些参考文章:

  1. Activity的onSaveInstanceState方法详解
  2. Recreating an Activity