@whosea
2017-11-12T12:02:36.000000Z
字数 7802
阅读 1745
本文连接地址:https://www.zybuluo.com/whosea/note/842034
墓碑机制是手机操作系统中的一个程序运行规则。说简单点,就是手机上一个任务被迫中断时(如有电话打入),系统记录下当前应用程序的状态后,(像把事件记录在墓碑上一样),然后中止程序。当需要恢复时,根据“墓碑”上的内容,将程序恢复到中断之前的状态。这样的一种机制就是“墓碑机制”
而这种方式在Android的表示形式为:在内存不够的情况下应用进入了后台,系统会有可能杀死这个Activity,用户切换回该应用时就会恢复当前Activity的内容。因此出现这种状况我们该怎么处理?
针对这种状况,系统自带的View或Fragment都已经帮我们实现了状态的自动保存与恢复,但是对于自己开发的自定义View,就需要去保存状态和恢复状态,这里系统提供了两个API方便我们去实现保存和恢复,分别是onSaveInstanceState和onRestoreInstanceState这两个方法。
简单说就是onSaveInstanceState和onRestoreInstanceState函数的调用时间
先说第五、六点,在屏幕切换之前,系统会销毁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,这是为什么呢?onSaveInstanceState与onRestoreInstanceState难道不是配对使用的?
首先在Android中,onSaveInstanceState是为了预防Activity被后台杀死的情况做的预处理,如果Activity没有被后台杀死,那么自然也就不需要进行现场的恢复,也就不会调用onRestoreInstanceState,而大多数情况下,Activity不会那么快被杀死。
那么我们要如何测试这4种情况?
前4种要在唤醒时候调用onRestoreInstanceState,那前提是只有Activity或者App被异常杀死,走恢复流程时候才会被调用。
应用是如何知道我是被异常杀死的,由于底层涉猎不深,只能大概的描述下:应用被异常杀死后在重新打开,系统底层会判断该应用是否异常退出,接着把当时现场的数据传递给它,应用拿到数据后传给Activity,调用起onRestoreInstanceState,这是Framework里ActivityThread中启动Activity的源码:
private Activity performLaunchActivity(){...mInstrumentation.callActivityOnCreate(activity, r.state);r.activity = activity;r.stopped = true;if (!r.activity.mFinished) {activity.performStart();r.stopped = false;}if (!r.activity.mFinished) {if (r.state != null) {mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);}}if (!r.activity.mFinished) {activity.mCalled = false;mInstrumentation.callActivityOnPostCreate(activity, r.state);}}
可以看出,只有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。
目前统计线上的bug,偶尔会看到这样的一个bug:
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceStateat android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1842)at android.support.v4.app.FragmentManagerImpl.popBackStackImmediate(FragmentManager.java:775)at android.support.v4.app.FragmentActivity.onBackPressed(FragmentActivity.java:178)at android.app.Activity.onKeyUp(Activity.java:2282)at android.view.KeyEvent.dispatch(KeyEvent.java:3232)
尝试了网上各种方案,但总偶尔会出现,要解决这个bug,我们不妨先提出这几个问题:
首先定位问题,观察源码可以发现,它是在FragmentManager的checkStateLoss方法里面抛出错误。
private void checkStateLoss() {if (mStateSaved) {throw new IllegalStateException("Can not perform this action after onSaveInstanceState");}if (mNoTransactionsBecause != null) {throw new IllegalStateException("Can not perform this action inside of " + mNoTransactionsBecause);}}
我们根据该方法追溯上去。
@Overridepublic boolean popBackStackImmediate() {checkStateLoss();executePendingTransactions();return popBackStackState(mActivity.mHandler, null, -1, 0);}
很明显的看出,popBackStackImmediate这个出栈的方法调用之前会去检查状态是否改变,然后再去执行Fragment操作。
继续追踪,看看到底是谁调用了。
FragmentActivity的onBackPressed:
public void onBackPressed() {if (!mFragments.popBackStackImmediate()) {supportFinishAfterTransition();}}
来源找到了,接着分析为什么出现错误。
观察上述代码,产生该错误的原因是mStateSaved变量为true,而这个变量是从哪里设置的呢?
我们从Activity调用onSaveInstanceState方法开始,该方法先保存view的状态
protected void onSaveInstanceState(Bundle outState) {outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());// view树的状态保存完之后,处理fragment相关的Parcelable p = mFragments.saveAllState();if (p != null) {outState.putParcelable(FRAGMENTS_TAG, p);}getApplication().dispatchActivitySaveInstanceState(this, outState);}
接着调用mFragments.saveAllState();该方法里面对mStateSaved进行的设置true操作。
Parcelable saveAllState() {// Make sure all pending operations have now been executed to get// our state update-to-date.execPendingActions();mStateSaved = true;if (mActive == null || mActive.size() <= 0) {return null;}...}
而这个方法里面一系列操作都是保存fragment的状态。
除了在onSaveInstanceState中设置以外,在onStop中也把mStateSaved置为true。
public void dispatchStop() {// See saveAllState() for the explanation of this. We do this for// all platform versions, to keep our behavior more consistent between// them.mStateSaved = true;moveToState(Fragment.STOPPED, false);}
那么什么时候才把mStateSaved设置为false呢。
回到Activity的onCreate方法里,这里可以发现它调用了Fragment的dispatchCreate方法,dispatchCreate把mStateSaved设置为false。
protected void onCreate(@Nullable Bundle savedInstanceState) {...mFragments.dispatchCreate();getApplication().dispatchActivityCreated(this, savedInstanceState);if (mVoiceInteractor != null) {mVoiceInteractor.attachActivity(this);}mCalled = true;}
同理既然onCreate有设置,那么resume也有做设置
final void performResume() {performRestart();...mFragments.dispatchResume();mFragments.execPendingActions();onPostResume();...}
以下几个方法是FragmentManager源码抽取的,被上述方法调用。
public void dispatchCreate() {mStateSaved = false;moveToState(Fragment.CREATED, false);}public void dispatchStart() {mStateSaved = false;moveToState(Fragment.STARTED, false);}public void dispatchResume() {mStateSaved = false;moveToState(Fragment.RESUMED, false);}
至此,我们可以知道如果onBackPressed发生在onSavedInstanceState之后,那么就会出现上面的crash。
重载onBackPressed在里面做finish操作,这样可以避免使用到Fragment api的出栈操作,因为在super.onBackPressed方法里面调用了FragmentManager#popBackStackImmediate()。
在基类里面管理属于自己的mStateSaved,用它来控制是否要做onBackPressed操作。
public class FragmentStateLossActivity extends Activity {private boolean mStateSaved;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_fragment_state_loss);mStateSaved = false;}@Overrideprotected void onSaveInstanceState(Bundle outState) {// 不调用super对我们意义不大,还是会崩溃,而且会丢失现场super.onSaveInstanceState(outState);if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {mStateSaved = true;}}@Overrideprotected void onResume() {super.onResume();mStateSaved = false;}@Overrideprotected void onPause() {super.onPause();}@Overrideprotected void onStop() {super.onStop();mStateSaved = true;}@Overrideprotected void onStart() {super.onStart();mStateSaved = false;}@Overrideprotected void onDestroy() {super.onDestroy();}@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) {if (!mStateSaved) {return super.onKeyDown(keyCode, event);} else {// State already saved, so ignore the eventreturn true;}}@Overridepublic void onBackPressed() {if (!mStateSaved) {super.onBackPressed();}}}
最后从上述问题我们可以知道:
1.为什么要在一些生命周期之前完成Fragment的commit操作
onCreate里面完成onPostResume里面完成(onPostResume是在onResume后调用的,确保Activity加载完毕,mStateSaved状态已经改变)onPause之前完成(onPause能确保在onSaveInstanceState之前执行)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