问题情景:
我们的应用在前台运行,无论怎么测试,我们的应用都是正常没问题的,这时按下home键,手机回到桌面,我们的应用进入后台,过了一段时间,我们把应用从后台切换回前台,这时测试时发现应用崩溃,出现了异常,异常信息里说某一个变量或者某一个对象为NULL,这是因为变量或者对象被系统内存回收机制GC掉了。这个问题自己测试的时候很难被发现,如果手机的内存不紧张的话,一般不会遇到这个问题,所以异常信息的收集变的很重要,不论应用安装在哪部手机上,只要出现程序中未捕获的异常时,就记录到手机本地,甚至上传到服务器,对APP的升级很有帮助。点击查看 捕获异常
出现问题的情况:
- 按下home键回桌面
- 从后台切换应用
- 按下电源键锁屏
- 屏幕横竖方向切换
问题分析:
当我们的应用进入后台,系统可能因为内存紧张而杀掉activity,在activity被杀掉之前调用onSaveInstanceState()保存每个实例的状态,保证等到我们切换到前台时实例状态可以在onCreate(Bundle)或者onRestoreInstanceState(Bundle) (传入的Bundle参数是由onSaveInstanceState封装好的)中恢复。在这个过程其实系统有时不会确保把全部的实例都保存下来了,所以就造成了我们遇到的异常。
下面一张图看清问题出在何时
知道原因了解决起来就有目的性了,我们可以手动的把丢失的数据提前保存起来,等到切换到前台时我们再手动的取出来
问题转移到:onSaveInstanceState()和onRestoreInstanceState (Bundle outState)
onSaveInstanceState()如果被调用,这个方法会在onStop()前被触发,但系统并不保证是否在onPause()之前或者之后触发。onRestoreInstanceState方法是在onstart之后执行,需要注意的是,onSaveInstanceState方法和onRestoreInstanceState方法“不一定”是成对的被调用的。 onRestoreInstanceState被调用的前提是,activity “确实”被系统销毁了,如果应用进入后台的时间比较短暂,activity的onRestoreInstanceState方法不会被执行。另外,onRestoreInstanceState的bundle参数也会传递到onCreate方法中,你也可以选择在onCreate方法中做数据还原。
其实即使应用到后台一段时间,我们也不一定会遇到上述的异常,因为系统在内存宽裕的时候不会回收我们的activity,而且系统即使要回收内存也是有优先级的,在Android系统中,为了使系统能够正确决定内存不足时终止那个进程从而回收内存,Android会依据进程中当前活跃组件的重要程度来尽可能高的估量一个进程的级别,根据级别把进程装入“Importance Hierarchy(重要性分级)“中, “Importance Hierarchy”中的进程是按重要程度排序的,优先级如下:
1.前台进程(Foreground)
前台进程是与用于进行交互的进程,在程序运行中仅有少量的进程处于前台,不同的组件会通过不同的方式将进程移植前台,当内存无法供给前台进程同时运行时候,部分前台进程才被杀死,用于维持用户界面响应。*
2.可见进程(Visible)
可见进程不在前台显示,但用户可以看到他的Activity,当一个Activity被调用onPause()方法时,它的进程就处于可见状态。可见进程一般不允许被终止,当前系统内存不足阻塞前台进程时,会终止可见进程释放内存供前台进程使用。
3.服务进程(Service)
服务进程是用户不可以直接看到进程,虽然用户不能看到该进程的执行,但该进程做着用户关心的问题,例如,在后台播放音乐。系统会一直维护该进程运行,除非系统内存不足时,无法维护前台进程和可见进程,系统将杀死该类进程。
4.后台进程(Background)
后台进程有一个用户看不到的Activity,该Activity的onStop()方法被系统调用,Activity处于停止状态,对用户的体验没有影响,系统可以在任何时刻杀死该类进程回收其内存供前台进程、可见进程、服务进程使用。系统中通常存在较多该进程保存在LRU中,当内存不足时系统回收LRU中用户最早使用的后台进程。
5.空进程(Empty)
该进程的设立是为了提高程序运行速度,组件再次运行时虚拟机不需要重新为组件分配内存。系统经常会杀死这种进程会杀死这种进程中优先级较低的进程以保持进程缓存和系统内核缓存之间的平衡。
按home键,应用程序进入后台,调用Onstop() 方法,当系统内存不足的时候,系统会回收该进程中onSaveInstanceState没有保存的变量(即便变量的引用没有断开),从而导致数据丢失,但是Activity没有被 finish掉,再次进入前天进程,就会出现数据丢失的现象。
解决办法:
- 提高应用的优先级防止被KILL掉
①相较于/data/app下的应用,放在/system/app下的应用享受更多的特权,比如若在其Manifest.xml文件中设置persistent属性为true,则可使其免受out-of-memory killer的影响。如应用程序'Phone'的AndroidManifest.xml文件:
<application android:name="PhoneApp"
android:persistent="true"
android:label="@string/dialerIconLabel"
android:icon="@drawable/ic_launcher_phone">
...
</application>
②设置后app提升为系统核心级别,任何情况下不会被kill掉, settings->applications里面也会屏蔽掉stop操作。
用法<activity android:alwaysRetainTaskState="true/false"></activity>
用来标记Activity所在的Task的状态是否总是由系统来保持——“true”,表示总是;“false”,表示在某种情形下允许系统恢复Task 到它的初始化状态。默认值是“false”。这个特性只针对Task的根Activity有意义;对其它Activity来说,忽略之。
- 手动保存变量
<span style="font-size:14px;">@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putBoolean("MyBoolean", true);
savedInstanceState.putDouble("myDouble", 1.9);
savedInstanceState.putInt("MyInt", 1);
savedInstanceState.putString("MyString", "Welcome back to Android");
// etc.
super.onSaveInstanceState(savedInstanceState);
}
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
boolean myBoolean = savedInstanceState.getBoolean("MyBoolean");
double myDouble = savedInstanceState.getDouble("myDouble");
int myInt = savedInstanceState.getInt("MyInt");
String myString = savedInstanceState.getString("MyString");
}</span>
另外,如果不是Activity而是Fragment页面又该如何解决呢?看下面代码
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("value", mValue);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
//读取保存的值
mValue = savedInstanceState.getInt("value", 0);
}
}
从上面的代码可以看出我们可以保存一些普通的数据类型,但是,如果我们需要保存自定义的Object该如何做呢?
①保存自定义对象的ArrayList
参考文章:
http://blog.csdn.net/braddoris/article/details/41115229
http://www.th7.cn/Program/Android/201503/404472.shtml
http://blog.csdn.net/hqdoremi/article/details/26376797