解决安卓屏幕旋转所带来的问题

为什么屏幕旋转会带来问题

在屏幕旋转时,系统会销毁当前活动的activity,并在旋转后重新创建一个新的activity,
那么旋转前activity中的一些数据就会丢失.所以只要找到一个方法能实现旋转前保存某些数据,
并在新创建的activity中可以取出之前保存的数据,也就解决了这个问题.

然而保存数据并不是万能的,有些操作是不能中断的,比如播放音乐,这就要用到另一种方法.

两个不同的例子

场景一:答题

当用户正在回答一些题目,每回答完一道题就保存用户填写的信息到文件中并切换出下一道题的view,此时如果旋转了屏幕,
那么旋转后标志用户正在填写哪一道题的对象就会丢失,而不得不从第一题重来.
这种场景适合使用OnSaveInstanceState(Bundle savedInstanceState)方式来解决,使用方法很简单,
只需要在activity中重写上述方法即可:

1
2
3
4
5
6
7
8
9
10
11
@Override
public void OnSaveInstanceState(Bundle savedInstanceState){
//这一句应该保留,因为这个方法默认是有一些行为的
super.OnSaveInstanceState(savedInstanceState);

//下面是我们重写新增的需要保存的数据
//使用Bundle的savedInstanceState对象来存数数据
//比如上述场景需要保存一个标志题号的数据:"index"
savedInstanceState.putInt("index",5);
//也就是以键值对的形式保存int型数据`5`
}

之后在onCreate方法中取出这个保存的数据即可.

需要注意的是我们在Bundle中保存的只能是基本数据类型以及可以实现Serializable接口的对象,
也就是说如果需要保存一个类的对象时,需要让这个类实现Serializable接口.

场景二:播放音乐

上面那个重写OnSaveInstanceState(Bundle savedInstanceState)的解决方法即使用于activity也适用于fragment,
虽然能恢复旋转前的数据,但由于被销毁了,终究是会中断一段时间的,如果当前activity或fragment的作用是
播放音乐,即便可以实现旋转后恢复播放进度,但仍就会停顿一下,这太影响用户体验了.
所以也就有了这个场景二,这个方法只适用于fragment,这个方法使得这个fragment在屏幕旋转时
被保留下来,不会销毁它,那么这个fragment的一些成员变量就都不会丢失了,并且在屏幕旋转期间依旧继续工作.
在本例中保留这个fragment就可以使得它的成员变量MediaPlayer的实例一直存在并正常工作.
要想保留一个fragment只需要在fragment的onCreate方法中添加一行代码即可:

1
setRetainInstance(true);

默认情况下上述方法的参数为false,也就是说默认情况下fragment会被销毁并重建.
保留的fragment利用了这样一个事实:
可以销毁和重建fragment的视图,但无需销毁fragment自身
fragment都是由fragmentManager管理的,在设备旋转时fragmentManager总是销毁fragment的视图,
因为需要根据设备的属性重新配置视图,但在尝试销毁fragment前会检测上述方法的参数,
如果为true则不销毁这个fragment,当托管这个fragment的activity重建后fragmentManager就找到未消毁的fragment
并为它生成新的视图;反之销毁,并重建视图和fragment.
也就是说这个fragment有一段时间是没有任何activity来托管它的,
需要注意的是这段时间非常短暂!,而且如果在这段时间内,系统需要回收内存那么就会销毁掉这个fragment.

两种方法的主要区别

第一种方法: OnSaveInstanceState(Bundle savedInstanceState)
第二种方法: setRetainInstance(true)

  • 第一种方法

    • 既可以用于activity,也可以用于fragment
    • 主要用于保存和恢复应用的UI状态
    • 只能存储基本类型数据或以实现Serializable接口的类
  • 第二种方法

    • 只能用于fragment
    • 数据保存较时间第一种方法短,并且有可能被系统回收内存时销毁
    • 由于保存对象时fragment所以无需关心fragment中数据的类型和某个类是否实现了Serializable接口