Android墓碑機制
本文連接地址:http://www.itdecent.cn/p/f5be35aaed32
一、墓碑定義
墓碑機制是手機操作系統(tǒng)中的一個程序運行規(guī)則。說簡單點,就是手機上一個任務(wù)被迫中斷時(如有電話打入),系統(tǒng)記錄下當(dāng)前應(yīng)用程序的狀態(tài)后,(像把事件記錄在墓碑上一樣),然后中止程序。當(dāng)需要恢復(fù)時,根據(jù)“墓碑”上的內(nèi)容,將程序恢復(fù)到中斷之前的狀態(tài)。這樣的一種機制就是“墓碑機制”
二、墓碑的保存與恢復(fù)
而這種方式在Android的表示形式為:在內(nèi)存不夠的情況下應(yīng)用進入了后臺,系統(tǒng)會有可能殺死這個Activity,用戶切換回該應(yīng)用時就會恢復(fù)當(dāng)前Activity的內(nèi)容。因此出現(xiàn)這種狀況我們該怎么處理?
針對這種狀況,系統(tǒng)自帶的View或Fragment都已經(jīng)幫我們實現(xiàn)了狀態(tài)的自動保存與恢復(fù),但是對于自己開發(fā)的自定義View,就需要去保存狀態(tài)和恢復(fù)狀態(tài),這里系統(tǒng)提供了兩個API方便我們?nèi)崿F(xiàn)保存和恢復(fù),分別是onSaveInstanceState和onRestoreInstanceState這兩個方法。
三、如何觸發(fā)墓碑機制
簡單說就是onSaveInstanceState和onRestoreInstanceState函數(shù)的調(diào)用時間
當(dāng)用戶按下HOME鍵時
長按HOME鍵,選擇運行其他的程序時
按下電源按鍵(關(guān)閉屏幕顯示)時
從activity A中啟動一個新的activity時
屏幕方向切換時,例如從豎屏切換到橫屏?xí)r
語言的切換
先說第五、六點,在屏幕切換之前,系統(tǒng)會銷毀activity A,在屏幕切換之后系統(tǒng)又會自動地創(chuàng)建activity A,所以onSaveInstanceState()一定會被執(zhí)行,且也一定會執(zhí)行onRestoreInstanceState()。
針對第五、六點打印的數(shù)據(jù)(activity A所發(fā)生的生命周期):
MainActivity: onPause
MainActivity: onSaveInstanceState
MainActivity: onStop
MainActivity: onDestroy
MainActivity: onCreate
MainActivity: onStart
MainActivity: onRestoreInstanceState
MainActivity: onResume
回到前4點,每次觸發(fā)都會調(diào)用onSaveInstanceState,但是再次喚醒卻不一定調(diào)用onRestoreInstanceState,這是為什么呢?onSaveInstanceState與onRestoreInstanceState難道不是配對使用的?
首先在Android中,onSaveInstanceState是為了預(yù)防Activity被后臺殺死的情況做的預(yù)處理,如果Activity沒有被后臺殺死,那么自然也就不需要進行現(xiàn)場的恢復(fù),也就不會調(diào)用onRestoreInstanceState,而大多數(shù)情況下,Activity不會那么快被殺死。
那么我們要如何測試這4種情況?
四、如何調(diào)試
前4種要在喚醒時候調(diào)用onRestoreInstanceState,那前提是只有Activity或者App被異常殺死,走恢復(fù)流程時候才會被調(diào)用。
應(yīng)用是如何知道我是被異常殺死的,由于底層涉獵不深,只能大概的描述下:應(yīng)用被異常殺死后在重新打開,系統(tǒng)底層會判斷該應(yīng)用是否異常退出,接著把當(dāng)時現(xiàn)場的數(shù)據(jù)傳遞給它,應(yīng)用拿到數(shù)據(jù)后傳給Activity,調(diào)用起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回調(diào)OnRestoreInstanceState,而r.state就是ActivityManagerService通過Binder傳給ActivityThread數(shù)據(jù),主要用來做場景恢復(fù)。
那我們要怎么測試這種情況呢?
- 開發(fā)者模式下勾選不保留活動選擇
該方式是為了方便測試,在開發(fā)者模式下勾選不保留活動選擇,這樣應(yīng)用的Activity進入后臺就不會保留,從而執(zhí)行onSaveInstanceState,再次恢復(fù)到前臺執(zhí)行onRestoreInstanceState。

- 內(nèi)存不足下觸發(fā)OOM
先修改模擬起的內(nèi)存大小,然后在打開新的Activity里面加載大數(shù)據(jù),不斷打開新界面,這時候內(nèi)存會不斷增多,直到超出系統(tǒng)可分配的內(nèi)存,導(dǎo)致OOM并提示錯誤,確認后系統(tǒng)會殺掉應(yīng)用釋放內(nèi)存,這時候會重新恢復(fù)界面。
打印日志如下:
MainActivity: onCreate
MainActivity: onStart
MainActivity: onRestoreInstanceState
MainActivity: onResume
- 直接殺掉應(yīng)用
按Home把當(dāng)前應(yīng)用放到后臺,然后從Android Studio進入Devive Monitor,選擇當(dāng)前應(yīng)用,接著選stop按鈕。
如圖:

這時恢復(fù)應(yīng)用時就會觸發(fā)onRestoreInstanceState。
五、關(guān)于onSaveInstanceState的探討
目前統(tǒng)計線上的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)
嘗試了網(wǎng)上各種方案,但總偶爾會出現(xiàn),要解決這個bug,我們不妨先提出這幾個問題:
錯誤是在哪里出現(xiàn)的
錯誤來源
為什么會出現(xiàn)這個錯誤
如何解決
1、錯誤是在哪里出現(xiàn)的
首先定位問題,觀察源碼可以發(fā)現(xiàn),它是在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);
}
}
2、錯誤來源
我們根據(jù)該方法追溯上去。
@Override
public boolean popBackStackImmediate() {
checkStateLoss();
executePendingTransactions();
return popBackStackState(mActivity.mHandler, null, -1, 0);
}
很明顯的看出,popBackStackImmediate這個出棧的方法調(diào)用之前會去檢查狀態(tài)是否改變,然后再去執(zhí)行Fragment操作。
繼續(xù)追蹤,看看到底是誰調(diào)用了。
FragmentActivity的onBackPressed:
public void onBackPressed() {
if (!mFragments.popBackStackImmediate()) {
supportFinishAfterTransition();
}
}
來源找到了,接著分析為什么出現(xiàn)錯誤。
3、為什么會出現(xiàn)這個錯誤
觀察上述代碼,產(chǎn)生該錯誤的原因是mStateSaved變量為true,而這個變量是從哪里設(shè)置的呢?
我們從Activity調(diào)用onSaveInstanceState方法開始,該方法先保存view的狀態(tài)
protected void onSaveInstanceState(Bundle outState) {
outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
// view樹的狀態(tài)保存完之后,處理fragment相關(guān)的
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
getApplication().dispatchActivitySaveInstanceState(this, outState);
}
接著調(diào)用mFragments.saveAllState();該方法里面對mStateSaved進行的設(shè)置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的狀態(tài)。
除了在onSaveInstanceState中設(shè)置以外,在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設(shè)置為false呢。
回到Activity的onCreate方法里,這里可以發(fā)現(xiàn)它調(diào)用了Fragment的dispatchCreate方法,dispatchCreate把mStateSaved設(shè)置為false。
protected void onCreate(@Nullable Bundle savedInstanceState) {
...
mFragments.dispatchCreate();
getApplication().dispatchActivityCreated(this, savedInstanceState);
if (mVoiceInteractor != null) {
mVoiceInteractor.attachActivity(this);
}
mCalled = true;
}
同理既然onCreate有設(shè)置,那么resume也有做設(shè)置
final void performResume() {
performRestart();
...
mFragments.dispatchResume();
mFragments.execPendingActions();
onPostResume();
...
}
以下幾個方法是FragmentManager源碼抽取的,被上述方法調(diào)用。
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發(fā)生在onSavedInstanceState之后,那么就會出現(xiàn)上面的crash。
4、如何解決
重載
onBackPressed在里面做finish操作,這樣可以避免使用到Fragmentapi的出棧操作,因為在super.onBackPressed方法里面調(diào)用了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) {
// 不調(diào)用super對我們意義不大,還是會崩潰,而且會丟失現(xiàn)場
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.為什么要在一些生命周期之前完成Fragment的commit操作
在
onCreate里面完成在
onPostResume里面完成(onPostResume是在onResume后調(diào)用的,確保Activity加載完畢,mStateSaved狀態(tài)已經(jīng)改變)在
onPause之前完成(onPause能確保在onSaveInstanceState之前執(zhí)行)
2.小心控制異步任務(wù),盡可能避免在一些生命周期函數(shù)中使用異步方法來調(diào)用commit,如AsyncTask 等。
3.使用commitAllowingStateLoss,它的意思是在狀態(tài)丟失是不會拋出異常,但在一些必須確保狀態(tài)被保存的場合下,盡量不使用commitAllowingStateLoss方法。它只能預(yù)防在create Fragment時候出現(xiàn)的問題,但是不能解決destroy Fragment時候出現(xiàn)的問題。
六、總結(jié)
1.了解了安卓的狀態(tài)保存與恢復(fù)大致流程
2.如何觸發(fā)安卓的狀態(tài)恢復(fù)
3.解決因為安卓的狀態(tài)保存導(dǎo)致出現(xiàn)的異常
參考資料
http://www.itdecent.cn/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/