我们的移动开发之路

Android墓碑机制

2018-04-23  本文已影响0人  whosea

Android墓碑机制

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

一、墓碑定义

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

二、墓碑的保存与恢复

而这种方式在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的源码:


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

先修改模拟起的内存大小,然后在打开新的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、如何解决


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操作

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

上一篇下一篇

猜你喜欢

热点阅读