Android墓碑机制

作者: whosea | 来源:发表于2018-04-23 09:14 被阅读0次

    Android墓碑机制

    本文连接地址:https://www.jianshu.com/p/f5be35aaed32

    一、墓碑定义

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

    二、墓碑的保存与恢复

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

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

    三、如何触发墓碑机制

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

    • 当用户按下HOME键时

    • 长按HOME键,选择运行其他的程序时

    • 按下电源按键(关闭屏幕显示)时

    • 从activity A中启动一个新的activity时

    • 屏幕方向切换时,例如从竖屏切换到横屏时

    • 语言的切换

    先说第五、六点,在屏幕切换之前,系统会销毁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的源码:

    
    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

    image
    • 内存不足下触发OOM

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

    打印日志如下:

    MainActivity: onCreate
    
    MainActivity: onStart
    
    MainActivity: onRestoreInstanceState
    
    MainActivity: onResume
    
    • 直接杀掉应用

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

    如图:

    image

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

    五、关于onSaveInstanceState的探讨

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

    
    java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
    
    at 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,我们不妨先提出这几个问题:

    • 错误是在哪里出现的

    • 错误来源

    • 为什么会出现这个错误

    • 如何解决

    1、错误是在哪里出现的

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

    
    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);
    
        }
    
    }
    
    

    2、错误来源

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

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

    很明显的看出,popBackStackImmediate这个出栈的方法调用之前会去检查状态是否改变,然后再去执行Fragment操作。

    继续追踪,看看到底是谁调用了。

    FragmentActivityonBackPressed:

    
    public void onBackPressed() {
    
        if (!mFragments.popBackStackImmediate()) {
    
            supportFinishAfterTransition();
    
        }
    
    }
    
    

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

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

    观察上述代码,产生该错误的原因是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呢。

    回到ActivityonCreate方法里,这里可以发现它调用了FragmentdispatchCreate方法,dispatchCreatemStateSaved设置为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。

    4、如何解决

    • 重载onBackPressed在里面做finish操作,这样可以避免使用到Fragment api的出栈操作,因为在super.onBackPressed方法里面调用了FragmentManager#popBackStackImmediate()

    • 在基类里面管理属于自己的mStateSaved,用它来控制是否要做onBackPressed操作。

    
    public class FragmentStateLossActivity extends Activity {
    
        private boolean mStateSaved;
    
        @Override
    
        protected void onCreate(Bundle savedInstanceState) {
    
            super.onCreate(savedInstanceState);
    
            setContentView(R.layout.activity_fragment_state_loss);
    
            mStateSaved = false;
    
        }
    
        @Override
    
        protected void onSaveInstanceState(Bundle outState) {
    
            // 不调用super对我们意义不大,还是会崩溃,而且会丢失现场
    
            super.onSaveInstanceState(outState);
    
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
    
                mStateSaved = true;
    
            }
    
        }
    
        @Override
    
        protected void onResume() {
    
            super.onResume();
    
            mStateSaved = false;
    
        }
    
        @Override
    
        protected void onPause() {
    
            super.onPause();
    
        }
    
        @Override
    
        protected void onStop() {
    
            super.onStop();
    
            mStateSaved = true;
    
        }
    
        @Override
    
        protected void onStart() {
    
            super.onStart();
    
            mStateSaved = false;
    
        }
    
        @Override
    
        protected void onDestroy() {
    
            super.onDestroy();
    
        }
    
        @Override
    
        public boolean onKeyDown(int keyCode, KeyEvent event) {
    
            if (!mStateSaved) {
    
                return super.onKeyDown(keyCode, event);
    
            } else {
    
                // State already saved, so ignore the event
    
                return true;
    
            }
    
        }
    
        @Override
    
        public void onBackPressed() {
    
            if (!mStateSaved) {
    
                super.onBackPressed();
    
            }
    
        }
    
    }
    
    

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

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

    • 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

    测试项目

    https://github.com/whosea/TestSaveInstance

    相关文章

      网友评论

        本文标题:Android墓碑机制

        本文链接:https://www.haomeiwen.com/subject/lnlklftx.html