[关闭]
@whosea 2017-11-12T12:02:36.000000Z 字数 7802 阅读 1487

Android墓碑机制

本文连接地址:https://www.zybuluo.com/whosea/note/842034

一、墓碑定义

墓碑机制是手机操作系统中的一个程序运行规则。说简单点,就是手机上一个任务被迫中断时(如有电话打入),系统记录下当前应用程序的状态后,(像把事件记录在墓碑上一样),然后中止程序。当需要恢复时,根据“墓碑”上的内容,将程序恢复到中断之前的状态。这样的一种机制就是“墓碑机制”

二、墓碑的保存与恢复

而这种方式在Android的表示形式为:在内存不够的情况下应用进入了后台,系统会有可能杀死这个Activity,用户切换回该应用时就会恢复当前Activity的内容。因此出现这种状况我们该怎么处理?

针对这种状况,系统自带的View或Fragment都已经帮我们实现了状态的自动保存与恢复,但是对于自己开发的自定义View,就需要去保存状态和恢复状态,这里系统提供了两个API方便我们去实现保存和恢复,分别是onSaveInstanceStateonRestoreInstanceState这两个方法。

三、如何触发墓碑机制

简单说就是onSaveInstanceStateonRestoreInstanceState函数的调用时间

先说第五、六点,在屏幕切换之前,系统会销毁activity A,在屏幕切换之后系统又会自动地创建activity A,所以onSaveInstanceState()一定会被执行,且也一定会执行onRestoreInstanceState()

针对第五、六点打印的数据(activity A所发生的生命周期):

MainActivity: onPause
MainActivity: onSaveInstanceState
MainActivity: onStop
MainActivity: onDestroy

MainActivity: onCreate
MainActivity: onStart
MainActivity: onRestoreInstanceState
MainActivity: onResume

回到前4点,每次触发都会调用onSaveInstanceState,但是再次唤醒却不一定调用onRestoreInstanceState,这是为什么呢?onSaveInstanceStateonRestoreInstanceState难道不是配对使用的?

首先在Android中,onSaveInstanceState是为了预防Activity被后台杀死的情况做的预处理,如果Activity没有被后台杀死,那么自然也就不需要进行现场的恢复,也就不会调用onRestoreInstanceState,而大多数情况下,Activity不会那么快被杀死。
那么我们要如何测试这4种情况?

四、如何调试

前4种要在唤醒时候调用onRestoreInstanceState,那前提是只有Activity或者App被异常杀死,走恢复流程时候才会被调用。

应用是如何知道我是被异常杀死的,由于底层涉猎不深,只能大概的描述下:应用被异常杀死后在重新打开,系统底层会判断该应用是否异常退出,接着把当时现场的数据传递给它,应用拿到数据后传给Activity,调用起onRestoreInstanceState,这是Framework里ActivityThread中启动Activity的源码:

  1. private Activity performLaunchActivity(){
  2. ...
  3. mInstrumentation.callActivityOnCreate(activity, r.state);
  4. r.activity = activity;
  5. r.stopped = true;
  6. if (!r.activity.mFinished) {
  7. activity.performStart();
  8. r.stopped = false;
  9. }
  10. if (!r.activity.mFinished) {
  11. if (r.state != null) {
  12. mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
  13. }
  14. }
  15. if (!r.activity.mFinished) {
  16. activity.mCalled = false;
  17. mInstrumentation.callActivityOnPostCreate(activity, r.state);
  18. }
  19. }

可以看出,只有r.state != null的时候,才通过mInstrumentation.callActivityOnRestoreInstanceState回调OnRestoreInstanceState,而r.state就是ActivityManagerService通过Binder传给ActivityThread数据,主要用来做场景恢复。

那我们要怎么测试这种情况呢?

该方式是为了方便测试,在开发者模式下勾选不保留活动选择,这样应用的Activity进入后台就不会保留,从而执行onSaveInstanceState,再次恢复到前台执行onRestoreInstanceState

先修改模拟起的内存大小,然后在打开新的Activity里面加载大数据,不断打开新界面,这时候内存会不断增多,直到超出系统可分配的内存,导致OOM并提示错误,确认后系统会杀掉应用释放内存,这时候会重新恢复界面。

打印日志如下:

MainActivity: onCreate
MainActivity: onStart
MainActivity: onRestoreInstanceState
MainActivity: onResume

按Home把当前应用放到后台,然后从Android Studio进入Devive Monitor,选择当前应用,接着选stop按钮。
如图:

这时恢复应用时就会触发onRestoreInstanceState

五、关于onSaveInstanceState的探讨

目前统计线上的bug,偶尔会看到这样的一个bug:

  1. java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
  2. at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1842)
  3. at android.support.v4.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:775)
  4. at android.support.v4.app.FragmentActivity.onBackPressed(FragmentActivity.java:178)
  5. at android.app.Activity.onKeyUp(Activity.java:2282)
  6. at android.view.KeyEvent.dispatch(KeyEvent.java:3232)

尝试了网上各种方案,但总偶尔会出现,要解决这个bug,我们不妨先提出这几个问题:

1、错误是在哪里出现的

首先定位问题,观察源码可以发现,它是在FragmentManagercheckStateLoss方法里面抛出错误。

  1. private void checkStateLoss() {
  2. if (mStateSaved) {
  3. throw new IllegalStateException(
  4. "Can not perform this action after onSaveInstanceState");
  5. }
  6. if (mNoTransactionsBecause != null) {
  7. throw new IllegalStateException(
  8. "Can not perform this action inside of " + mNoTransactionsBecause);
  9. }
  10. }

2、错误来源

我们根据该方法追溯上去。

  1. @Override
  2. public boolean popBackStackImmediate() {
  3. checkStateLoss();
  4. executePendingTransactions();
  5. return popBackStackState(mActivity.mHandler, null, -1, 0);
  6. }

很明显的看出,popBackStackImmediate这个出栈的方法调用之前会去检查状态是否改变,然后再去执行Fragment操作。
继续追踪,看看到底是谁调用了。
FragmentActivityonBackPressed:

  1. public void onBackPressed() {
  2. if (!mFragments.popBackStackImmediate()) {
  3. supportFinishAfterTransition();
  4. }
  5. }

来源找到了,接着分析为什么出现错误。

3、为什么会出现这个错误

观察上述代码,产生该错误的原因是mStateSaved变量为true,而这个变量是从哪里设置的呢?
我们从Activity调用onSaveInstanceState方法开始,该方法先保存view的状态

  1. protected void onSaveInstanceState(Bundle outState) {
  2. outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
  3. // view树的状态保存完之后,处理fragment相关的
  4. Parcelable p = mFragments.saveAllState();
  5. if (p != null) {
  6. outState.putParcelable(FRAGMENTS_TAG, p);
  7. }
  8. getApplication().dispatchActivitySaveInstanceState(this, outState);
  9. }

接着调用mFragments.saveAllState();该方法里面对mStateSaved进行的设置true操作。

  1. Parcelable saveAllState() {
  2. // Make sure all pending operations have now been executed to get
  3. // our state update-to-date.
  4. execPendingActions();
  5. mStateSaved = true;
  6. if (mActive == null || mActive.size() <= 0) {
  7. return null;
  8. }
  9. ...
  10. }

而这个方法里面一系列操作都是保存fragment的状态。
除了在onSaveInstanceState中设置以外,在onStop中也把mStateSaved置为true

  1. public void dispatchStop() {
  2. // See saveAllState() for the explanation of this. We do this for
  3. // all platform versions, to keep our behavior more consistent between
  4. // them.
  5. mStateSaved = true;
  6. moveToState(Fragment.STOPPED, false);
  7. }

那么什么时候才把mStateSaved设置为false呢。

回到ActivityonCreate方法里,这里可以发现它调用了FragmentdispatchCreate方法,dispatchCreatemStateSaved设置为false

  1. protected void onCreate(@Nullable Bundle savedInstanceState) {
  2. ...
  3. mFragments.dispatchCreate();
  4. getApplication().dispatchActivityCreated(this, savedInstanceState);
  5. if (mVoiceInteractor != null) {
  6. mVoiceInteractor.attachActivity(this);
  7. }
  8. mCalled = true;
  9. }

同理既然onCreate有设置,那么resume也有做设置

  1. final void performResume() {
  2. performRestart();
  3. ...
  4. mFragments.dispatchResume();
  5. mFragments.execPendingActions();
  6. onPostResume();
  7. ...
  8. }

以下几个方法是FragmentManager源码抽取的,被上述方法调用。

  1. public void dispatchCreate() {
  2. mStateSaved = false;
  3. moveToState(Fragment.CREATED, false);
  4. }
  5. public void dispatchStart() {
  6. mStateSaved = false;
  7. moveToState(Fragment.STARTED, false);
  8. }
  9. public void dispatchResume() {
  10. mStateSaved = false;
  11. moveToState(Fragment.RESUMED, false);
  12. }

至此,我们可以知道如果onBackPressed发生在onSavedInstanceState之后,那么就会出现上面的crash。

4、如何解决

  1. public class FragmentStateLossActivity extends Activity {
  2. private boolean mStateSaved;
  3. @Override
  4. protected void onCreate(Bundle savedInstanceState) {
  5. super.onCreate(savedInstanceState);
  6. setContentView(R.layout.activity_fragment_state_loss);
  7. mStateSaved = false;
  8. }
  9. @Override
  10. protected void onSaveInstanceState(Bundle outState) {
  11. // 不调用super对我们意义不大,还是会崩溃,而且会丢失现场
  12. super.onSaveInstanceState(outState);
  13. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
  14. mStateSaved = true;
  15. }
  16. }
  17. @Override
  18. protected void onResume() {
  19. super.onResume();
  20. mStateSaved = false;
  21. }
  22. @Override
  23. protected void onPause() {
  24. super.onPause();
  25. }
  26. @Override
  27. protected void onStop() {
  28. super.onStop();
  29. mStateSaved = true;
  30. }
  31. @Override
  32. protected void onStart() {
  33. super.onStart();
  34. mStateSaved = false;
  35. }
  36. @Override
  37. protected void onDestroy() {
  38. super.onDestroy();
  39. }
  40. @Override
  41. public boolean onKeyDown(int keyCode, KeyEvent event) {
  42. if (!mStateSaved) {
  43. return super.onKeyDown(keyCode, event);
  44. } else {
  45. // State already saved, so ignore the event
  46. return true;
  47. }
  48. }
  49. @Override
  50. public void onBackPressed() {
  51. if (!mStateSaved) {
  52. super.onBackPressed();
  53. }
  54. }
  55. }

最后从上述问题我们可以知道:

1.为什么要在一些生命周期之前完成Fragmentcommit操作

2.小心控制异步任务,尽可能避免在一些生命周期函数中使用异步方法来调用commit,如AsyncTask 等。

3.使用commitAllowingStateLoss,它的意思是在状态丢失是不会抛出异常,但在一些必须确保状态被保存的场合下,尽量不使用commitAllowingStateLoss方法。它只能预防在create Fragment时候出现的问题,但是不能解决destroy Fragment时候出现的问题。

六、总结

1.了解了安卓的状态保存与恢复大致流程
2.如何触发安卓的状态恢复
3.解决因为安卓的状态保存导致出现的异常

参考资料

http://www.jianshu.com/p/6e3e0176f74d
http://blog.csdn.net/a553181867/article/details/54600695
http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html
http://toughcoder.net/blog/2016/11/28/fear-android-fragment-state-loss-no-more/
https://stackoverflow.com/questions/7469082/getting-exception-illegalstateexception-can-not-perform-this-action-after-onsa

测试项目

https://github.com/whosea/TestSaveInstance

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注