Android墓碑机制

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342