Android中的狀態(tài)保存和恢復(fù)
Android中的狀態(tài)保存和恢復(fù), 包括Activity和Fragment以及其中View的狀態(tài)處理.
Activity的狀態(tài)除了其中的View和Fragment的狀態(tài)之外, 還需要用戶(hù)手動(dòng)保存一些成員變量.
Fragment的狀態(tài)有它自己的實(shí)例狀態(tài)和其中的View狀態(tài), 因?yàn)槠渖芷诘撵`活性和實(shí)際需要的不同, 情況會(huì)多一些.
根據(jù)源碼, 列出了Fragment中實(shí)例狀態(tài)和View狀態(tài)保存和恢復(fù)的幾個(gè)入口, 便于分析查看.
最后專(zhuān)門(mén)講了WebView狀態(tài)保存和恢復(fù), 問(wèn)題及處理.
還有一個(gè)工具類(lèi)icepick的介紹.
Activity的狀態(tài)保存和恢復(fù)
作為熱身, 先來(lái)講一下Activity的狀態(tài)保存和恢復(fù).
什么時(shí)候需要恢復(fù)Activity
關(guān)于A(yíng)ctivity的銷(xiāo)毀和重建, 之前有這么一篇博文: Activity的重新創(chuàng)建
總結(jié)來(lái)說(shuō), 就是Activity的銷(xiāo)毀, 分為徹底銷(xiāo)毀和留下數(shù)據(jù)的銷(xiāo)毀兩種.
徹底銷(xiāo)毀是指用戶(hù)主動(dòng)去關(guān)閉或退出這個(gè)Activity. 此時(shí)是不需要狀態(tài)恢復(fù)的, 因?yàn)橄麓位貋?lái)又是重新創(chuàng)建全新的實(shí)例.
留下數(shù)據(jù)的銷(xiāo)毀是指系統(tǒng)銷(xiāo)毀了activity, 但是當(dāng)用戶(hù)返回來(lái)時(shí), 會(huì)重新創(chuàng)建它, 讓用戶(hù)覺(jué)得它一直都在.
屏幕旋轉(zhuǎn)重建可以歸結(jié)為第二種情況, 打開(kāi)Do not keep activities開(kāi)關(guān), 切換activities也是會(huì)出現(xiàn)第二種情況.
打開(kāi)Do not keep activities開(kāi)關(guān)就是為了模擬內(nèi)存不足時(shí)的系統(tǒng)行為, 這里有一篇分析
如何恢復(fù)
實(shí)際上系統(tǒng)已經(jīng)幫我們做好了View層面基本的恢復(fù)工作, 主要是依靠下面兩個(gè)方法:
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// 在onStop()之前調(diào)用, 文檔中說(shuō)并不保證在onPause()的之前還是之后
// 我的試驗(yàn)中一般是在onPause()之后
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
// 在onStart() 之后
}
Bundle其中包含了activity中的view和fragment的各種信息, 所以調(diào)用基類(lèi)的方法就可以完成基本的view層面的恢復(fù)工作.
注意這兩個(gè)方法并不是activity的生命周期回調(diào), 對(duì)于activity來(lái)說(shuō)它們不是一定會(huì)發(fā)生的.
另外需要注意的是, View必須要有id才能被恢復(fù).
舉一個(gè)實(shí)例來(lái)說(shuō)明:
Activity A start B, 那么A的onSaveInstanceState()
會(huì)在onStop()之前調(diào)用, 以防A被系統(tǒng)銷(xiāo)毀.
但是在B中按下back鍵finish()了自己后, B被銷(xiāo)毀的過(guò)程中, 并沒(méi)有調(diào)用onSaveInstanceState()
, 是因?yàn)锽并沒(méi)有被壓入task的back stack中,
也即系統(tǒng)知道B并不需要儲(chǔ)存自己的狀態(tài).
正常情況下, 返回到A, A沒(méi)有被銷(xiāo)毀, 也不會(huì)調(diào)用onRestoreInstanceState()
, 因?yàn)樗械臓顟B(tài)都還在, 并不需要重建.
如果我們打開(kāi)了Do not keep activities開(kāi)關(guān), 模擬系統(tǒng)內(nèi)存不足時(shí)的行為, 從A到B, 可以看到當(dāng)B resume的時(shí)候A會(huì)一路走到onDestroy(),
而關(guān)掉B之后, A會(huì)從onCreate()開(kāi)始走, 此時(shí)onCreate()的參數(shù)bundle就不為空了, onStart()之后會(huì)調(diào)用onRestoreInstanceState()
方法, 其參數(shù)bundle中內(nèi)容類(lèi)似于如下:
Bundle[{android:viewHierarchyState=Bundle[mParcelledData.dataSize=272]}]
其中包含了View的狀態(tài), 如果有Fragment, 也會(huì)包含F(xiàn)ragment的狀態(tài), 其實(shí)質(zhì)是保存了FragmentManagerState, 內(nèi)容類(lèi)似于如下:
Bundle[{android:viewHierarchyState=Bundle[{android:views={16908290=android.view.AbsSavedState$1@bc382e7, 2131492950=CompoundButton.SavedState{4034f96 checked=true}, 2131492951=android.view.AbsSavedState$1@bc382e7}}], android:fragments=android.app.FragmentManagerState@bacc717}]
對(duì)于上面的例子來(lái)說(shuō), B什么時(shí)候會(huì)調(diào)用onSaveInstanceState()
呢?
當(dāng)從A打開(kāi)B之后, 按下Home鍵, B就會(huì)調(diào)用onSaveInstanceState()
.
因?yàn)檫@時(shí)候系統(tǒng)不知道用戶(hù)什么時(shí)候會(huì)返回, 有可能會(huì)把B也銷(xiāo)毀了, 所以保存一下它的狀態(tài).
如果下次回來(lái)它沒(méi)有被重建, onRestoreInstanceState()
就不會(huì)被調(diào)用, 如果它被重建了, onRestoreInstanceState()
才會(huì)被調(diào)用.
Activity保存方法的調(diào)用時(shí)機(jī)
activity的onSaveInstanceState()
和onRestoreInstanceState()
方法在如下情形下會(huì)調(diào)用:
- 屏幕旋轉(zhuǎn)重建: 先save再restore.
- 啟動(dòng)另一個(gè)activity: 當(dāng)前activity在離開(kāi)前會(huì)save, 返回時(shí)如果因?yàn)楸幌到y(tǒng)殺死需要重建, 則會(huì)從onCreate()重新開(kāi)始生命周期, 調(diào)用onRestoreInstanceState(); 如果沒(méi)有重建, 則不會(huì)調(diào)用onCreate(), 也不會(huì)調(diào)用onRestoreInstanceState(), 生命周期從onRestart()開(kāi)始, 接著onStart()和onResume().
- 按Home鍵的情形和啟動(dòng)另一個(gè)activity一樣, 當(dāng)前activity在離開(kāi)前會(huì)save, 用戶(hù)再次點(diǎn)擊應(yīng)用圖標(biāo)返回時(shí), 如果重建發(fā)生, 則會(huì)調(diào)用onCreate()和onRestoreInstanceState(); 如果activity不需要重建, 只是onRestart(), 則不會(huì)調(diào)用onRestoreInstanceState().
Activity恢復(fù)方法的調(diào)用時(shí)機(jī)
activity的onSaveInstanceState()
和onRestoreInstanceState()
方法在如下情形下不會(huì)調(diào)用:
- 用戶(hù)主動(dòng)finish()掉的activity不會(huì)調(diào)用onSaveInstanceState(), 包括主動(dòng)按back退出的情況.
- 新建的activity, 從onCreate()開(kāi)始, 不會(huì)調(diào)用onRestoreInstanceState().
Activity中還需要手動(dòng)恢復(fù)什么
如上, 系統(tǒng)已經(jīng)為我們恢復(fù)了activity中的各種view和fragment, 那么我們自己需要保存和恢復(fù)一些什么呢?
答案是成員變量值.
因?yàn)橄到y(tǒng)并不知道你的各種成員變量有什么用, 哪些值需要保存, 所以需要你自己覆寫(xiě)上面兩個(gè)方法, 然后把自己需要保存的值加進(jìn)bundle里面去. 具體例子, 這里Activity的重新創(chuàng)建有, 我就不重復(fù)了.
重要的是不要忘記調(diào)用super的方法, 那里有系統(tǒng)幫我們恢復(fù)的工作.
工具類(lèi)Icepick介紹
在介紹下面的內(nèi)容之前, 先介紹一個(gè)小工具: Icepick
這個(gè)工具的作用是, 在你想保存和重建自己的成員變量數(shù)據(jù)時(shí), 幫你省去那些put和get方法的調(diào)用, 你也不用為每一個(gè)字段起一個(gè)常量key.
你需要做的就是簡(jiǎn)單地在你想要保存狀態(tài)的字段上面加上一個(gè)@State
注解.
然后在保存和恢復(fù)的時(shí)候分別加上一句話(huà):
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Icepick.restoreInstanceState(this, savedInstanceState);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Icepick.saveInstanceState(this, outState);
}
然后你的成員變量就有了它應(yīng)該有的值了, DONE!
Fragment的狀態(tài)保存和恢復(fù)
Fragment的狀態(tài)比Activity的要復(fù)雜一些, 因?yàn)樗纳芷跔顟B(tài)比較多.
Fragment狀態(tài)保存和恢復(fù)的相關(guān)方法
按照上面的思路, 我先去查找Fragment中保存和恢復(fù)的回調(diào)方法了.
Fragment的狀態(tài)保存回調(diào)是這個(gè)方法:
public void onSaveInstanceState(Bundle outState) {
// may be called any time before onDestroy()
}
這個(gè)方法和之前activity的情況大體是類(lèi)似的, 它不是生命周期的回調(diào), 所以只在有需要的時(shí)候會(huì)調(diào)到.
onSaveInstanceState()在activity調(diào)用onSaveInstanceState()的時(shí)候發(fā)生, 用于保存實(shí)例狀態(tài).(看它的方法名: instance state).
onSaveInstanceState()
方法保存的bundle會(huì)返回給幾個(gè)生命周期回調(diào): onCreate()
, onCreateView()
, onViewCreated()
和onActivityCreated()
.
Fragment并沒(méi)有對(duì)應(yīng)的onRestoreInstanceState()方法.
也即沒(méi)有實(shí)例狀態(tài)的恢復(fù)回調(diào).
Fragment只有一個(gè)onViewStateRestored()的回調(diào)方法:
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
// 在onActivityCreated()和onStart()之間調(diào)用
mCalled = true;
}
onViewStateRestored()每次新建Fragment都會(huì)發(fā)生.
它并不是實(shí)例狀態(tài)恢復(fù)的方法, 只是一個(gè)View狀態(tài)恢復(fù)的回調(diào).
這里需要注意, Fragment的狀態(tài)分兩個(gè)類(lèi)型: 實(shí)例狀態(tài)和View狀態(tài).
這里有個(gè)最佳實(shí)踐: The Real Best Practices to Save/Restore Activity's and Fragment's state
不要把Fragment的實(shí)例狀態(tài)和View狀態(tài)混在一起處理.
在這里我先上個(gè)結(jié)論, 把查看源碼中Fragment狀態(tài)保存和恢復(fù)的相關(guān)方法列出來(lái):
Fragment狀態(tài)保存入口:
Fragment的狀態(tài)保存入口有三個(gè):
- Activity的狀態(tài)保存, 在A(yíng)ctivity的
onSaveInstanceState()
里, 調(diào)用了FragmentManger的saveAllState()
方法, 其中會(huì)對(duì)mActive中各個(gè)Fragment的實(shí)例狀態(tài)和View狀態(tài)分別進(jìn)行保存. - FragmentManager還提供了public方法:
saveFragmentInstanceState()
, 可以對(duì)單個(gè)Fragment進(jìn)行狀態(tài)保存, 這是提供給我們用的, 后面會(huì)有例子介紹這個(gè). 其中調(diào)用的saveFragmentBasicState()
方法即為情況一中所用, 圖中已畫(huà)出標(biāo)記. - FragmentManager的
moveToState()
方法中, 當(dāng)狀態(tài)回退到ACTIVITY_CREATED
, 會(huì)調(diào)用saveFragmentViewState()
方法, 保存View的狀態(tài).
moveToState()
方法中有很長(zhǎng)的switch case, 中間不帶break, 基本是根據(jù)新?tīng)顟B(tài)和當(dāng)前狀態(tài)的比較, 分為正向創(chuàng)建和反向銷(xiāo)毀兩個(gè)方向, 一路沿著多個(gè)case走下去.
Fragment狀態(tài)恢復(fù)入口:
三個(gè)恢復(fù)的入口和三個(gè)保存的入口剛好對(duì)應(yīng).
- 在A(yíng)ctivity重新創(chuàng)建的時(shí)候, 恢復(fù)所有的Fragment狀態(tài).
- 如果調(diào)用了FragmentManager的方法:
saveFragmentInstanceState()
, 返回值得到的狀態(tài)可以用Fragment的setInitialSavedState()
方法設(shè)置給新的Fragment實(shí)例, 作為初始狀態(tài). - FragmentManager的
moveToState()
方法中, 當(dāng)狀態(tài)正向創(chuàng)建到CREATED
時(shí), Fragment自己會(huì)恢復(fù)View的狀態(tài).
這三個(gè)入口分別對(duì)應(yīng)的情況是:
入口1對(duì)應(yīng)系統(tǒng)銷(xiāo)毀和重建新實(shí)例.
入口2對(duì)應(yīng)用戶(hù)自定義銷(xiāo)毀和創(chuàng)建新Fragment實(shí)例的狀態(tài)傳遞.
入口3對(duì)應(yīng)同一Fragment實(shí)例自身的View狀態(tài)重建.
Fragment狀態(tài)保存恢復(fù)和Activity的聯(lián)系
這里對(duì)應(yīng)的是入口1的情況.
當(dāng)Activity在做狀態(tài)保存和恢復(fù)的時(shí)候, 在它其中的fragment自然也需要做狀態(tài)保存和恢復(fù).
所以Fragment的onSaveInstanceState()在activity調(diào)用onSaveInstanceState()的時(shí)候一定會(huì)發(fā)生.
同樣的, 如果Fragment中有一些成員變量的值在此時(shí)需要保存, 也可以用@State標(biāo)記, 處理方法和上面一樣.
也即, 在A(yíng)ctivity需要保存狀態(tài)的時(shí)候, 其中的Fragments的實(shí)例狀態(tài)自動(dòng)被處理保存.
Fragment同一實(shí)例的View狀態(tài)恢復(fù)
這里對(duì)應(yīng)的是入口3的情況.
前面介紹過(guò), activity在保存狀態(tài)的時(shí)候, 會(huì)將所有View和Fragment的狀態(tài)都保存起來(lái)等待重建的時(shí)候使用.
但是如果是單個(gè)Activity對(duì)應(yīng)多個(gè)Fragments的架構(gòu), Activity永遠(yuǎn)是resume狀態(tài), 多個(gè)Fragments在切換的過(guò)程中, 沒(méi)有activity的幫助, 如何保存自己的狀態(tài)?
首先, 取決于你的多個(gè)Fragments是如何初始化的.
我做了一個(gè)實(shí)驗(yàn), 在activity的onCreate()里面初始化兩個(gè)Fragment:
private void initFragments() {
tab1Fragment = getFragmentManager().findFragmentByTag(Tab1Fragment.TAG);
if (tab1Fragment == null) {
tab1Fragment = new Tab1Fragment();
}
tab2Fragment = getFragmentManager().findFragmentByTag(Tab2Fragment.TAG);
if (tab2Fragment == null) {
tab2Fragment = new Tab2Fragment();
}
}
然后點(diǎn)擊兩個(gè)按鈕來(lái)切換它們, replace(), 并且不加入到back stack中:
@OnClick(R.id.tab1)
void onTab1Clicked() {
getFragmentManager().beginTransaction()
.replace(R.id.content_container, tab1Fragment, Tab1Fragment.TAG)
.commit();
}
@OnClick(R.id.tab2)
void onTab2Clicked() {
getFragmentManager().beginTransaction()
.replace(R.id.content_container, tab2Fragment, Tab2Fragment.TAG)
.commit();
}
可以看到, 每一次的切換, 都是一個(gè)Fragment的完全destroy, detach和另一個(gè)fragment的attach, create,
但是當(dāng)我在這兩個(gè)fragment中各自加上EditText, 發(fā)現(xiàn)只要EditText有id, 切換過(guò)程中EditText的內(nèi)容是被保存的.
這是誰(shuí)在什么時(shí)候保存并恢復(fù)的呢?
我在TextChange的回調(diào)里打了斷點(diǎn), 發(fā)現(xiàn)調(diào)用棧如下:
在FragmentManagerImpl
中, moveToState()
方法的case Fragment.CREATED中:
調(diào)用了: f.restoreViewState(f.mSavedFragmentState);
此時(shí)我沒(méi)有做任何保存狀態(tài)的處理, 但是斷點(diǎn)中可以看出:
雖然mSavedFragmentState是null, 但是mSavedViewState卻有值.
所以這個(gè)View狀態(tài)保存和恢復(fù)對(duì)應(yīng)的入口即是上面兩個(gè)圖中的入口三.
這是因?yàn)槲业膬蓚€(gè)fragment只new了一次, 然后保存了成員變量, 即便是Fragment重新onCreate(), 但是對(duì)應(yīng)的實(shí)例仍然是同一個(gè).
這和Activity是不同的, 因?yàn)槟闶菬o(wú)法new一個(gè)Activity的.
在上面的例子中, 如果不保存Fragment的引用, 每次都new Fragment, 那么View的狀態(tài)是不會(huì)被保存的, 因?yàn)椴煌瑢?shí)例間的狀態(tài)傳遞只有在系統(tǒng)銷(xiāo)毀恢復(fù)的情況下才會(huì)發(fā)生(入口一).
如果我們需要在不同的實(shí)例間傳遞狀態(tài), 就需要用到下面的方法:
不同F(xiàn)ragment實(shí)例間的狀態(tài)保存和恢復(fù)
這里對(duì)應(yīng)的是入口2, 不同于入口1和3, 它們是自動(dòng)的, 入口2是用戶(hù)主動(dòng)保存和恢復(fù)的情形.
自己主動(dòng)保存Fragment的狀態(tài), 可以調(diào)用FragmentManager的這個(gè)方法:
public abstract Fragment.SavedState saveFragmentInstanceState(Fragment f);
它的實(shí)現(xiàn)是這樣的:
@Override
public Fragment.SavedState saveFragmentInstanceState(Fragment fragment) {
if (fragment.mIndex < 0) {
throwException(new IllegalStateException("Fragment " + fragment
+ " is not currently in the FragmentManager"));
}
if (fragment.mState > Fragment.INITIALIZING) {
Bundle result = saveFragmentBasicState(fragment);
return result != null ? new Fragment.SavedState(result) : null;
}
return null;
}
返回的數(shù)據(jù)類(lèi)型是: Fragment.SavedState, 這個(gè)state可以通過(guò)Fragment的這個(gè)方法設(shè)置給自己:
public void setInitialSavedState(SavedState state) {
if (mIndex >= 0) {
throw new IllegalStateException("Fragment already active");
}
mSavedFragmentState = state != null && state.mState != null
? state.mState : null;
}
但是注意只能在Fragment被加入之前設(shè)置, 這是一個(gè)初始狀態(tài).
利用這兩個(gè)方法可以更加自由地保存和恢復(fù)狀態(tài), 而不依賴(lài)于A(yíng)ctivity.
這樣處理以后, 不必保存Fragment的引用, 每次切換的時(shí)候雖然都new了新的實(shí)例, 但是舊的實(shí)例的狀態(tài)可以設(shè)置給新實(shí)例.
例子代碼:
@State
SparseArray<Fragment.SavedState> savedStateSparseArray = new SparseArray<>();
void onTab1Clicked() {
// save current tab
Fragment tab2Fragment = getSupportFragmentManager().findFragmentByTag(Tab2Fragment.TAG);
if (tab2Fragment != null) {
saveFragmentState(1, tab2Fragment);
}
// restore last state
Tab1Fragment tab1Fragment = new Tab1Fragment();
restoreFragmentState(0, tab1Fragment);
// show new tab
getSupportFragmentManager().beginTransaction()
.replace(R.id.content_container, tab1Fragment, Tab1Fragment.TAG)
.commit();
}
private void saveFragmentState(int index, Fragment fragment) {
Fragment.SavedState savedState = getSupportFragmentManager().saveFragmentInstanceState(fragment);
savedStateSparseArray.put(index, savedState);
}
private void restoreFragmentState(int index, Fragment fragment) {
Fragment.SavedState savedState = savedStateSparseArray.get(index);
fragment.setInitialSavedState(savedState);
}
注意這里用了SparseArray來(lái)存儲(chǔ)Fragment的狀態(tài), 并且加上了@State
, 這樣在A(yíng)ctivity重建的時(shí)候其中的內(nèi)容也能夠被恢復(fù).
Back stack中的fragment
有一點(diǎn)很特殊的是, 當(dāng)Fragment從back stack中返回, 實(shí)際上是經(jīng)歷了一次View的銷(xiāo)毀和重建, 但是它本身并沒(méi)有被重建.
即View狀態(tài)需要重建, 實(shí)例狀態(tài)不需要重建.
舉個(gè)例子說(shuō)明這種情形: Fragment被另一個(gè)Fragment replace(), 并且壓入back stack中, 此時(shí)它的View是被銷(xiāo)毀的, 但是它本身并沒(méi)有被銷(xiāo)毀.
也即, 它走到了onDestroyView(), 卻沒(méi)有走onDestroy()
和onDetact()
.
等back回來(lái)的時(shí)候, 它的view會(huì)被重建, 重新從onCreateView()開(kāi)始走生命周期.
在這整個(gè)過(guò)程中, 該Fragment中的成員變量是保持不變的, 只有View會(huì)被重新創(chuàng)建.
在這個(gè)過(guò)程中, instance state的saving并沒(méi)有發(fā)生.
所以, 很多時(shí)候Fragment還需要考慮的是在沒(méi)有Activity幫助的情形下(Activity并沒(méi)有可能重建的情形), 自身View狀態(tài)的保存.
此時(shí)要注意一些不容易發(fā)現(xiàn)的錯(cuò)誤, 比如List的新實(shí)例需要重新setAdapter等.
Fragment setRetainInstance
Fragment有一個(gè)相關(guān)方法:
setRetainInstance
這個(gè)方法設(shè)置為true的時(shí)候表示, 即便activity重建了, 但是fragment的實(shí)例并不被重建.
注意此方法只對(duì)沒(méi)有放在back stack中的fragment生效.
什么時(shí)候要用這個(gè)方法呢? 處理configuration change的時(shí)候:
Handling Configuration Changes with Fragments
這樣, 當(dāng)屏幕旋轉(zhuǎn), Activity重建, 但是其中的fragment和fragment正在執(zhí)行的任務(wù)不必重建.
更多解釋可以參見(jiàn):
http://stackoverflow.com/questions/11182180/understanding-fragments-setretaininstanceboolean
http://stackoverflow.com/questions/11160412/why-use-fragmentsetretaininstanceboolean
注意這個(gè)方法只是針對(duì)configuration change, 并不影響用戶(hù)主動(dòng)關(guān)閉和系統(tǒng)銷(xiāo)毀的情況:
當(dāng)activity被用戶(hù)主動(dòng)finish, 其中的所有fragments仍然會(huì)被銷(xiāo)毀.
當(dāng)activity不在最頂端, memory不夠了, 系統(tǒng)仍然可能會(huì)銷(xiāo)毀activity和其中的fragments.
View的狀態(tài)保存和恢復(fù)
View的狀態(tài)保存和恢復(fù)主要是依賴(lài)于下面幾個(gè)方法:
保存: saveHierarchyState()
-> dispatchSaveInstanceState()
-> onSaveInstanceState()
恢復(fù): restoreHierarchyState()
-> dispatchRestoreInstanceState()
-> onRestoreInstanceState()
還有兩個(gè)重要的前提條件是View要有id, 并且setSavedEnabled()
為true.(這個(gè)值默認(rèn)為true).
在系統(tǒng)的widget里(比如TextView, EditText, Checkbox等), 這些都是已經(jīng)被處理好的, 我們只需要給View賦予id, Activity和Fragment重建的時(shí)候會(huì)自動(dòng)恢復(fù)其中的狀態(tài). (這里的Fragment恢復(fù)對(duì)應(yīng)入口一和入口三, 入口二屬于跨實(shí)例新建的情況).
但是如果你要使用第三方的自定義View, 就需要確認(rèn)一下它們內(nèi)部是否有狀態(tài)保存和恢復(fù)的代碼.
如果不行你就需要繼承該自定義View, 然后實(shí)現(xiàn)這兩個(gè)方法:
//
// Assumes that SomeSmartButton is a 3rd Party view that
// View State Saving/Restoring are not implemented internally
//
public class SomeBetterSmartButton extends SomeSmartButton {
...
@Override
public Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle();
// Save current View's state here
return bundle;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
super.onRestoreInstanceState(state);
// Restore View's state here
}
...
}
WebView的狀態(tài)保存和恢復(fù)
WebView的狀態(tài)保存和恢復(fù)不像其他原生View一樣是自動(dòng)完成的.
WebView不是繼承自View的.
如果我們把WebView放在布局里, 不加處理, 那么Activity或Fragment重建的過(guò)程中, WebView的狀態(tài)就會(huì)丟失, 變成初始狀態(tài).
在Fragment的onSaveInstanceState()里面可以加入如下代碼來(lái)保存WebView的狀態(tài):
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
webView.saveState(outState);
}
然后在初始化的時(shí)候, 增加判斷, 不必每次都打開(kāi)初始鏈接:
if (savedInstanceState != null) {
webView.restoreState(savedInstanceState);
} else {
webView.loadUrl(TEST_URL);
}
這樣處理以后, 在重新建立的時(shí)候, WebView的狀態(tài)就能恢復(fù)到離開(kāi)前的頁(yè)面.
不論WebView是放在A(yíng)ctivity里還是Fragment里, 這個(gè)方法都適用.
但是Fragment還有另一種情況, 即Fragment被壓入back stack, 此時(shí)它沒(méi)有被destroy(), 所以沒(méi)有調(diào)用onSavedInstanceState()這個(gè)方法.
這種情況返回的時(shí)候, 會(huì)從onCreateView()開(kāi)始, 并且savedInstanceState為null, 于是其中WebView之前的狀態(tài)在此時(shí)丟失了.
解決這種情況可以利用Fragment實(shí)例并未銷(xiāo)毀的條件, 增加一個(gè)成員變量bundle, 保存WebView的狀態(tài), 最終解決如下:
private Bundle webViewState;
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ButterKnife.bind(this, view);
initWebView();
if (webViewState != null) {
//Fragment實(shí)例并未被銷(xiāo)毀, 重新create view
webView.restoreState(webViewState);
} else if (savedInstanceState != null) {
//Fragment實(shí)例被銷(xiāo)毀重建
webView.restoreState(savedInstanceState);
} else {
//全新Fragment
webView.loadUrl(TEST_URL);
}
}
@Override
public void onPause() {
super.onPause();
webView.onPause();
//Fragment不被銷(xiāo)毀(Fragment被加入back stack)的情況下, 依靠Fragment中的成員變量保存WebView狀態(tài)
webViewState = new Bundle();
webView.saveState(webViewState);
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//Fragment被銷(xiāo)毀的情況, 依靠outState保存WebView狀態(tài)
if (webView != null) {
webView.saveState(outState);
}
}
本文完整例子相關(guān)實(shí)驗(yàn)代碼可見(jiàn):
HelloActivityAndFragment
中的State Restore Demo.
參考資料
Developer Android:
Android Fragment Reference
Android FragmentManager Reference
Posts:
Recreating an Activity
Activity的重新創(chuàng)建
從源碼角度剖析Fragment核心知識(shí)點(diǎn)
Fragment源碼閱讀筆記
The Real Best Practices to Save/Restore Activity's and Fragment's state
Android中保存和恢復(fù)Fragment狀態(tài)的最好方法
Handling Configuration Changes with Fragments
Saving Android View state correctly
Tools:
icepick