Android墓碑機(jī)制
本文連接地址:http://www.reibang.com/p/f5be35aaed32
一、墓碑定義
墓碑機(jī)制是手機(jī)操作系統(tǒng)中的一個(gè)程序運(yùn)行規(guī)則枯夜。說簡單點(diǎn)价捧,就是手機(jī)上一個(gè)任務(wù)被迫中斷時(shí)(如有電話打入),系統(tǒng)記錄下當(dāng)前應(yīng)用程序的狀態(tài)后,(像把事件記錄在墓碑上一樣)遗增,然后中止程序。當(dāng)需要恢復(fù)時(shí)款青,根據(jù)“墓碑”上的內(nèi)容做修,將程序恢復(fù)到中斷之前的狀態(tài)。這樣的一種機(jī)制就是“墓碑機(jī)制”
二抡草、墓碑的保存與恢復(fù)
而這種方式在Android
的表示形式為:在內(nèi)存不夠的情況下應(yīng)用進(jìn)入了后臺饰及,系統(tǒng)會有可能殺死這個(gè)Activity
,用戶切換回該應(yīng)用時(shí)就會恢復(fù)當(dāng)前Activity
的內(nèi)容康震。因此出現(xiàn)這種狀況我們該怎么處理燎含?
針對這種狀況,系統(tǒng)自帶的View或Fragment都已經(jīng)幫我們實(shí)現(xiàn)了狀態(tài)的自動保存與恢復(fù)腿短,但是對于自己開發(fā)的自定義View屏箍,就需要去保存狀態(tài)和恢復(fù)狀態(tài)绘梦,這里系統(tǒng)提供了兩個(gè)API方便我們?nèi)?shí)現(xiàn)保存和恢復(fù),分別是onSaveInstanceState
和onRestoreInstanceState
這兩個(gè)方法铣除。
三谚咬、如何觸發(fā)墓碑機(jī)制
簡單說就是onSaveInstanceState
和onRestoreInstanceState
函數(shù)的調(diào)用時(shí)間
當(dāng)用戶按下HOME鍵時(shí)
長按HOME鍵,選擇運(yùn)行其他的程序時(shí)
按下電源按鍵(關(guān)閉屏幕顯示)時(shí)
從activity A中啟動一個(gè)新的activity時(shí)
屏幕方向切換時(shí)尚粘,例如從豎屏切換到橫屏?xí)r
語言的切換
先說第五择卦、六點(diǎn),在屏幕切換之前郎嫁,系統(tǒng)會銷毀activity A秉继,在屏幕切換之后系統(tǒng)又會自動地創(chuàng)建activity A,所以onSaveInstanceState()
一定會被執(zhí)行泽铛,且也一定會執(zhí)行onRestoreInstanceState()
尚辑。
針對第五、六點(diǎn)打印的數(shù)據(jù)(activity A所發(fā)生的生命周期):
MainActivity: onPause
MainActivity: onSaveInstanceState
MainActivity: onStop
MainActivity: onDestroy
MainActivity: onCreate
MainActivity: onStart
MainActivity: onRestoreInstanceState
MainActivity: onResume
回到前4點(diǎn)盔腔,每次觸發(fā)都會調(diào)用onSaveInstanceState
杠茬,但是再次喚醒卻不一定調(diào)用onRestoreInstanceState
,這是為什么呢弛随?onSaveInstanceState
與onRestoreInstanceState
難道不是配對使用的瓢喉?
首先在Android中,onSaveInstanceState
是為了預(yù)防Activity
被后臺殺死的情況做的預(yù)處理舀透,如果Activity
沒有被后臺殺死栓票,那么自然也就不需要進(jìn)行現(xiàn)場的恢復(fù),也就不會調(diào)用onRestoreInstanceState
愕够,而大多數(shù)情況下走贪,Activity
不會那么快被殺死。
那么我們要如何測試這4種情況惑芭?
四坠狡、如何調(diào)試
前4種要在喚醒時(shí)候調(diào)用onRestoreInstanceState
,那前提是只有Activity
或者App被異常殺死强衡,走恢復(fù)流程時(shí)候才會被調(diào)用擦秽。
應(yīng)用是如何知道我是被異常殺死的,由于底層涉獵不深漩勤,只能大概的描述下:應(yīng)用被異常殺死后在重新打開,系統(tǒng)底層會判斷該應(yīng)用是否異常退出缩搅,接著把當(dāng)時(shí)現(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
的時(shí)候置谦,才通過mInstrumentation.callActivityOnRestoreInstanceState
回調(diào)OnRestoreInstanceState
,而r.state
就是ActivityManagerService
通過Binder
傳給ActivityThread
數(shù)據(jù)亿傅,主要用來做場景恢復(fù)媒峡。
那我們要怎么測試這種情況呢?
- 開發(fā)者模式下勾選不保留活動選擇
該方式是為了方便測試葵擎,在開發(fā)者模式下勾選不保留活動選擇谅阿,這樣應(yīng)用的Activity
進(jìn)入后臺就不會保留,從而執(zhí)行onSaveInstanceState
酬滤,再次恢復(fù)到前臺執(zhí)行onRestoreInstanceState
签餐。
- 內(nèi)存不足下觸發(fā)OOM
先修改模擬起的內(nèi)存大小,然后在打開新的Activity
里面加載大數(shù)據(jù)盯串,不斷打開新界面氯檐,這時(shí)候內(nèi)存會不斷增多,直到超出系統(tǒng)可分配的內(nèi)存体捏,導(dǎo)致OOM并提示錯(cuò)誤冠摄,確認(rèn)后系統(tǒng)會殺掉應(yīng)用釋放內(nèi)存,這時(shí)候會重新恢復(fù)界面几缭。
打印日志如下:
MainActivity: onCreate
MainActivity: onStart
MainActivity: onRestoreInstanceState
MainActivity: onResume
- 直接殺掉應(yīng)用
按Home把當(dāng)前應(yīng)用放到后臺河泳,然后從Android Studio進(jìn)入Devive Monitor,選擇當(dāng)前應(yīng)用奏司,接著選stop按鈕乔询。
如圖:
這時(shí)恢復(fù)應(yīng)用時(shí)就會觸發(fā)onRestoreInstanceState
。
五韵洋、關(guān)于onSaveInstanceState的探討
目前統(tǒng)計(jì)線上的bug竿刁,偶爾會看到這樣的一個(gè)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)搪缨,要解決這個(gè)bug食拜,我們不妨先提出這幾個(gè)問題:
錯(cuò)誤是在哪里出現(xiàn)的
錯(cuò)誤來源
為什么會出現(xiàn)這個(gè)錯(cuò)誤
如何解決
1、錯(cuò)誤是在哪里出現(xiàn)的
首先定位問題副编,觀察源碼可以發(fā)現(xiàn)负甸,它是在FragmentManager
的checkStateLoss
方法里面拋出錯(cuò)誤。
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痹届、錯(cuò)誤來源
我們根據(jù)該方法追溯上去呻待。
@Override
public boolean popBackStackImmediate() {
checkStateLoss();
executePendingTransactions();
return popBackStackState(mActivity.mHandler, null, -1, 0);
}
很明顯的看出,popBackStackImmediate
這個(gè)出棧的方法調(diào)用之前會去檢查狀態(tài)是否改變队腐,然后再去執(zhí)行Fragment
操作蚕捉。
繼續(xù)追蹤,看看到底是誰調(diào)用了柴淘。
FragmentActivity
的onBackPressed
:
public void onBackPressed() {
if (!mFragments.popBackStackImmediate()) {
supportFinishAfterTransition();
}
}
來源找到了迫淹,接著分析為什么出現(xiàn)錯(cuò)誤秘通。
3、為什么會出現(xiàn)這個(gè)錯(cuò)誤
觀察上述代碼敛熬,產(chǎn)生該錯(cuò)誤的原因是mStateSaved
變量為true
肺稀,而這個(gè)變量是從哪里設(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
進(jìn)行的設(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;
}
...
}
而這個(gè)方法里面一系列操作都是保存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);
}
那么什么時(shí)候才把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();
...
}
以下幾個(gè)方法是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操作,這樣可以避免使用到Fragment
api的出棧操作形导,因?yàn)樵?code>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
時(shí)候出現(xiàn)的問題癣缅,但是不能解決destroy Fragment
時(shí)候出現(xiàn)的問題厨剪。
六、總結(jié)
1.了解了安卓的狀態(tài)保存與恢復(fù)大致流程
2.如何觸發(fā)安卓的狀態(tài)恢復(fù)
3.解決因?yàn)榘沧康臓顟B(tài)保存導(dǎo)致出現(xiàn)的異常
參考資料
http://www.reibang.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/