參考資料:
- http://www.cnblogs.com/mengdd/p/5582244.html
-
https://inthecheesefactory.com/blog/fragment-state-saving-best-practices/en
大部分內(nèi)存摘自上述博客,感謝原作者的分享;
開發(fā)中,由于狀態(tài)保存這種場景需要模擬,會造成了一定的開發(fā)成本,如:內(nèi)存不夠時,app被回收,喚醒時,可能出現(xiàn)錯誤情況;
Activity的銷毀與重建
- 正常情況:back鍵,與調(diào)用finish方法;
- 特殊情況:當Activity處于onStop狀態(tài)時,如:退到后臺,并且長時間不用時,極有可能會被系統(tǒng)回收,用來釋放一些內(nèi)存;
- 旋屏情況:如果Activity支持旋屏,每次旋屏都會導致activity的銷毀與重建;
特殊情況下
當activity回到前臺時,如果被回收了,此時,系統(tǒng)會重新創(chuàng)建新的Activity實例,并利用舊實例存下來的數(shù)據(jù)來恢復界面;這些數(shù)據(jù)稱為:instance state,存在Bundle對象中;
缺省狀態(tài)下,系統(tǒng)會把每一個View對象保存起來(比如EditText對象中的文本,ListView中的滾動條位置等(注意:需要提供android:id)),即如果activity實例被銷毀和重建,那么不需要你編碼,layout狀態(tài)會恢復到前次狀態(tài)。但是如果你的activity需要恢復更多的信息,比如成員變量信息,則需要自己動手寫了。
在這里就涉及到回調(diào)函數(shù)onSaveInstanceState(),注意 Activity onSaveInstanceState() 有2個重載方法,一般我們使用下面的:
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
系統(tǒng)會在用戶離開activity的時候調(diào)用這個函數(shù),并且傳遞給它一個Bundle object,如果系統(tǒng)稍后需要重建這個activity實例,它會傳遞同一個Bundle object到onRestoreInstanceState() 和 onCreate() 方法中去。
舉個例子:當ActivityA在前臺時,如果用戶按下home鍵,或者 打開一個新的ActivityB,或來電等情形下,系統(tǒng)會自動調(diào)用 onSaveInstanceState()方法;
Activity - onSaveInstanceState()觸發(fā)的2個情況
- 系統(tǒng)回收時,調(diào)用;喚醒時,執(zhí)行回調(diào)onRestoreXXX;
- 用戶離開Activity時,調(diào)用;喚醒時,如果系統(tǒng)未回收,不執(zhí)行onRestoreXXX;
** 存儲Activity狀態(tài)**
我們就在 onSaveInstanceState() 方法中來存儲狀態(tài),一定要調(diào)用super;
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putString(KEY_FRAGMENT_TAG, mFragmentCurrentTag);
super.onSaveInstanceState(outState);
}
恢復Activity狀態(tài)
當被回收喚醒時,會執(zhí)行 onCreate() 和onRestoreInstanceState()回調(diào)函數(shù);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
.....
.....
// 如果程序喚醒了
if (savedInstanceState != null) {
restoreFragments();
mFragmentCurrentTag = savedInstanceState.getString(KEY_FRAGMENT_TAG);
mIsSaveInstanceCalled = true;
}
Activity的數(shù)據(jù)加載
一般在onCreate中,加載Activity的數(shù)據(jù),其他回調(diào)方法很可能被調(diào)用,比如:如在onStart中加載了數(shù)據(jù),按home,馬上又回到頁面時,onStart會執(zhí)行;
示例代碼:
/**
* 自動記錄 滾動文字
*/
ListView listView;
/**
* 記錄內(nèi)容
*/
EditText et;
// 在這里初始化數(shù)據(jù)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_state1);
listView = (ListView) findViewById(R.id.list);
et = (EditText) findViewById(R.id.et_test);
Log.e(TAG, "onCreate: " + savedInstanceState);
String[] a = new String[255 - 64 + 1];
for (int i = 64; i < 255; i++) {
a[i-64] = ">>>>>" + ((char) i);
}
listView.setAdapter(new ArrayAdapter<>(getApplicationContext(), android.R.layout.simple_list_item_1, a));
}
@Override
protected void onStart() {
super.onStart();
}
Fragment的狀態(tài)保存和恢復
相對于Activity,F(xiàn)ragment的情況,就顯得特別復雜,如果有嵌套Fragment,則更復雜了。如果頁面不是特別復雜,能不用嵌套fragment,則不用;
** Fragment啟動時的生命周期回調(diào):**

** 按home鍵時:**

之所以執(zhí)行 onSaveInstanceXXX是因為Activity執(zhí)行了這個方法;
旋轉(zhuǎn)屏幕時(view的狀態(tài)自己維護了):

Fragment add 與 remove
remove()是移除fragment, 如果fragment不加入到back stack, remove()的時候, fragment的生命周期會一直走到onDetach().類似于 按 back,activity 正常結束一樣;
添加到 backStack就不一樣了;remove(), fragment 的生命會走到 onDestroyView(),不會執(zhí)行onDetach(),此時 fragment本身的實例是存在的,成員變量也存在,但是view銷毀了;不要把Fragment的實例狀態(tài)和View狀態(tài)混在一起處理,這點非常重要;
當Fragment從back stack中返回, 實際上是經(jīng)歷了一次View的銷毀和重建, 但是它本身并沒有被重建.
即View狀態(tài)需要重建, 實例狀態(tài)不需要重建.
當Fragment被另一個Fragment replace(), 并且壓入back stack中, 此時它的View是被銷毀的, 但是它本身并沒有被銷毀.
也即, 它走到了onDestroyView(), 卻沒有走onDestroy()和onDetact().
等back回來的時候, 它的view會被重建, 重新從onCreateView()開始走生命周期.
在這整個過程中, 該Fragment中的成員變量是保持不變的, 只有View會被重新創(chuàng)建.
在這個過程中, instance state的saving并沒有發(fā)生.
我們來看看:
// 添加FragmentB
findViewById(R.id.addFragmentB).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment f = fragmentManager.findFragmentByTag(FragmentB.class.getName());
if (f == null) {
f = Fragment.instantiate(getApplicationContext(), FragmentB.class.getName());
}
// 如果不添加返回棧,remove() 該fragment實例會銷毀的
fragmentManager.beginTransaction().add(R.id.container, f, FragmentB.class.getName())
.addToBackStack(null).commit();
}
});
// 移除FragmentB
findViewById(R.id.removeFragmentB).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment f = fragmentManager.findFragmentByTag(FragmentB.class.getName());
if (f != null) {
fragmentManager.beginTransaction().remove(f).commit();
}
}
});
上面的代碼,我們第一段添加FragmentB,第二段,移除FragmentB,我們打印一下生命周期方法:

可以看到Fragment并沒有回到onDetach,onDestroy,也即:fragment 是其對應的View消耗了。但是Fragment的示例還是存在的;
下面顯示再次add FragmentB,打印如下:

這個時候我們看看成員變量吧:

如果FragmentB不添加返回鍵,調(diào)用remove(),就類似按返回鍵一樣了,會直接消耗FragmentB,這個機制跟Activity是一致的;
Fragment onCreateView多次執(zhí)行
了解了上面之后,也就明白為什么 onCreateView會多次執(zhí)行了吧。
常見的做法,是記錄 一個 rootView來記錄一下 onCreateView中返回的view,下一次Fragment onCreateView回調(diào)時,判斷rootView是否為null,來進行是否加載數(shù)據(jù),等其他操作;
如下代碼:
private View rootView;//緩存Fragment view
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if(rootView==null){
rootView=inflater.inflate(R.layout.tab_fragment, null);
}
//緩存的rootView需要判斷是否已經(jīng)被加過parent,
// 如果有parent需要從parent刪除,要不然會發(fā)生這個rootview已經(jīng)有parent的錯誤。
ViewGroup parent = (ViewGroup) rootView.getParent();
if (parent != null) {
parent.removeView(rootView);
}
return rootView;
}
這樣可以解決問題,特別是界面layout元素特別多的時候,這樣可以看上去可以避免 inflate多次執(zhí)行一樣,的確可以這樣,但就是看著特別別扭
但google這樣設計,或許是有其他考慮的吧,這里不是很明白;
;如果界面真的切換頻率過高,可以考慮使用 hide與show來操作了,來避免上面的代碼;
如果不考慮上面的實現(xiàn)方式,我們完全可以不加判斷來做,讓其直接 inflate吧,但是,這里 界面上view元素的 狀態(tài)是如何恢復的呢?也沒看到,調(diào)用onSaveInstacneXX等之類的方法,還是從大神博客中,找到了;
Fragment狀態(tài)保存入口:####
摘自:http://www.cnblogs.com/mengdd/p/5582244.html

3個入口:
- Activity的狀態(tài)保存, 在Activity的onSaveInstanceState()里, 調(diào)用了FragmentManger的saveAllState()方法, 其中會對mActive中各個Fragment的實例狀態(tài)和View狀態(tài)分別進行保存.
- FragmentManager還提供了public方法: saveFragmentInstanceState(), 可以對單個Fragment進行狀態(tài)保存, 這是提供給我們用的, 其中調(diào)用的saveFragmentBasicState()方法即為情況一中所用, 圖中已畫出標記.
- FragmentManager的moveToState()方法中, 當狀態(tài)回退到ACTIVITY_CREATED, 會調(diào)用saveFragmentViewState()方法, 保存View的狀態(tài).
Fragment狀態(tài)恢復入口:####

三個恢復的入口和三個保存的入口剛好對應.
- 在Activity重新創(chuàng)建的時候, 恢復所有的Fragment狀態(tài).
- 如果調(diào)用了FragmentManager的方法: saveFragmentInstanceState(), 返回值得到的狀態(tài)可以用Fragment的setInitialSavedState()方法設置給新的Fragment實例, 作為初始狀態(tài).
- FragmentManager的moveToState()方法中, 當狀態(tài)正向創(chuàng)建到CREATED時, Fragment自己會恢復View的狀態(tài).
這三個入口分別對應的情況是:
- 入口1對應系統(tǒng)銷毀和重建新實例.
- 入口2對應用戶自定義銷毀和創(chuàng)建新Fragment實例的狀態(tài)傳遞.
- 入口3對應同一Fragment實例自身的View狀態(tài)重建.
Fragment狀態(tài)保存恢復和Activity關聯(lián):
對應入口1的情況,類似于Activity狀態(tài)保存于恢復處理;比較好理解,不進行分析了;
Fragment同一實例的View狀態(tài)恢復
對應入口3的情況,也即:activity是resume狀態(tài)下,切換fragment,是如何保存自己的狀態(tài)的?
Fragment被add過,當remove()此fragment時,發(fā)現(xiàn) view 的 onSaveInstanceState會被調(diào)用,調(diào)用棧如下:

因為 commit不是立刻執(zhí)行,所以跟蹤的堆棧,commit那部分調(diào)用丟失了,在這里,可以看到 moveToState, saveFragmentViewState調(diào)用了,這也就說明了,remove時,fragment其內(nèi)部的view會保存狀態(tài);
來看看立即執(zhí)行 commit的調(diào)用棧:

不同F(xiàn)ragment實例間的狀態(tài)保存和恢復
入口1與入口3都是自動處理,入口2需要用戶手動來處理;
如果需要在不同fragment實例間傳遞狀態(tài),就需要用到入口2了,手動調(diào)用
FragmentManager 的 saveFragmentInstanceState 方法:
public abstract Fragment.SavedState saveFragmentInstanceState(Fragment f);
// 具體實現(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;
}
恢復時,調(diào)用Fragment setInitialSavedState 來實現(xiàn);
/**
* Set the initial saved state that this Fragment should restore itself
* from when first being constructed, as returned by
* {@link FragmentManager#saveFragmentInstanceState(Fragment)
* FragmentManager.saveFragmentInstanceState}.
*
* @param state The state the fragment should be restored from.
*/
public void setInitialSavedState(SavedState state) {
if (mIndex >= 0) {
throw new IllegalStateException("Fragment already active");
}
mSavedFragmentState = state != null && state.mState != null
? state.mState : null;
}
注意: 只能在Fragment被加入之前設置(add, replace).
利用這兩個方法可以更加自由地保存和恢復狀態(tài), 而不依賴于Activity.
這樣處理以后, 不必保存Fragment的引用, 每次切換的時候雖然都new了新的實例, 但是舊的實例的狀態(tài)可以設置給新實例.
詳細例子請參考:
http://www.cnblogs.com/mengdd/p/5582244.html
final String STATE_ = "state_";
// 存儲fragment狀態(tài)
SparseArray<Fragment.SavedState> savedStateSparseArray = new SparseArray<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_state_restore_demo);
// 恢復
if (savedInstanceState != null) {
savedStateSparseArray = savedInstanceState.getSparseParcelableArray(STATE_);
}
// 切換
findViewById(R.id.tab1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// save current tab
Fragment tab2Fragment = getSupportFragmentManager().findFragmentByTag(FragmentF.class.getName());
if (tab2Fragment != null) {
// 保存 tab2Fragment的狀態(tài)
saveFragmentState(1, tab2Fragment);
}
// restore last state, 每次都new
FragmentE tab1Fragment = new FragmentE();
restoreFragmentState(0, tab1Fragment);
// show new tab
getSupportFragmentManager().beginTransaction()
.replace(R.id.content_container, tab1Fragment, FragmentE.class.getName())
.commit();
}
});
findViewById(R.id.tab2).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Fragment tab1Fragment = getSupportFragmentManager().findFragmentByTag(FragmentE.class.getName());
if (tab1Fragment != null) {
saveFragmentState(0, tab1Fragment);
}
// 每次都new
FragmentF tab2Fragment = new FragmentF();
restoreFragmentState(1, tab2Fragment);
getSupportFragmentManager().beginTransaction()
.replace(R.id.content_container, tab2Fragment, FragmentF.class.getName())
.commit();
}
});
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putSparseParcelableArray(STATE_, savedStateSparseArray);
}
/**
* 手動存狀態(tài)
*
* @param index
* @param fragment
*/
private void saveFragmentState(int index, Fragment fragment) {
Fragment.SavedState savedState = getSupportFragmentManager().saveFragmentInstanceState(fragment);
savedStateSparseArray.put(index, savedState);
}
/**
* 手動調(diào)用 恢復狀態(tài)
*
* @param index
* @param fragment
*/
private void restoreFragmentState(int index, Fragment fragment) {
Fragment.SavedState savedState = savedStateSparseArray.get(index);
fragment.setInitialSavedState(savedState);
}