緣起
之前已經(jīng)做了關(guān)于fragment源碼的分析烫幕,但貌似把fragment關(guān)于保存砸喻、恢復(fù)的內(nèi)容給忽略了欢瞪,再加上上周5在開發(fā)一個(gè)功能時(shí)遇到了一個(gè)奇怪的現(xiàn)象敌完,當(dāng)時(shí)真是怎么也想不明白,而且單步調(diào)試發(fā)現(xiàn)狀態(tài)也都是對(duì)的钝荡,但是界面渲染完就又不對(duì)了街立,呃。埠通。赎离。
這里先解釋下我那個(gè)功能的大概樣子:fragment包括一個(gè)可以滾動(dòng)的LinearLayout,可以動(dòng)態(tài)添加多個(gè)item view端辱,item view是包括了一個(gè)checkbox梁剔,多個(gè)textview這樣的東西,同時(shí)只有一個(gè)item view可以被選中舞蔽。奇怪的現(xiàn)象是:fragment第2次show出來的時(shí)候荣病,默認(rèn)應(yīng)該選中的項(xiàng)沒有被選中,第1次就完全沒這個(gè)問題渗柿,這里我重用了同一個(gè)fragment的實(shí)例个盆。
此現(xiàn)象的原因分析
沒辦法這個(gè)問題的原因不查出來,真是吃飯都走神。老話講颊亮,大膽假設(shè)柴梆,小心求證。既然checkbox的狀態(tài)不對(duì)终惑,那么我立馬感覺只能是checkbox的setChecked方法被我還不知道的地方調(diào)用了绍在,于是我將系統(tǒng)的checkbox換成了自己繼承的MyCheckbox,然后override了setChecked方法狠鸳,并在那里加了些log揣苏,再次run了一遍悯嗓,果然不出所料件舵,輸出了一堆日志。那就證明了我的猜想脯厨,確實(shí)是不知道什么地方再次調(diào)用了setChecked方法铅祸,覆蓋了我自己在onCreateView里面關(guān)于checkbox的設(shè)置。接下來就很簡單了合武,我在重載的方法那里掛了個(gè)斷點(diǎn)临梗,很快就知道了調(diào)用棧,如下:
在本文的最后稼跳,我們?cè)賮碓敿?xì)分析為什么會(huì)發(fā)生這個(gè)行為盟庞。
fragment保存、恢復(fù)過程
在View樹的狀態(tài)保存汤善、恢復(fù)一文中我們已經(jīng)分析了絕大部分的內(nèi)容什猖,那里忽略了關(guān)于fragment的代碼,我們?cè)谶@里著重講解下红淡,來回顧下Activity.onSaveInstanceState
方法:
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);
}
protected void onCreate(@Nullable Bundle savedInstanceState) {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState);
if (mLastNonConfigurationInstances != null) {
mFragments.restoreLoaderNonConfig(mLastNonConfigurationInstances.loaders);
}
if (mActivityInfo.parentActivityName != null) {
if (mActionBar == null) {
mEnableDefaultActionBarUp = true;
} else {
mActionBar.setDefaultDisplayHomeAsUpEnabled(true);
}
}
if (savedInstanceState != null) {
Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
// 恢復(fù)fragment相關(guān)的狀態(tài)
mFragments.restoreAllState(p, mLastNonConfigurationInstances != null
? mLastNonConfigurationInstances.fragments : null);
}
mFragments.dispatchCreate();
getApplication().dispatchActivityCreated(this, savedInstanceState);
if (mVoiceInteractor != null) {
mVoiceInteractor.attachActivity(this);
}
mCalled = true;
}
從上面的源碼可以看出,fragment的狀態(tài)保存在旱、恢復(fù)是根據(jù)外面的act來進(jìn)行的摇零,也就是說它的調(diào)用時(shí)機(jī)是和act一致的。
mFragments.saveAllState();
這行代碼最終會(huì)調(diào)到FragmentManagerImpl.saveAllState
方法桶蝎,代碼如下:
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;
}
// First collect all active fragments.
int N = mActive.size();
FragmentState[] active = new FragmentState[N];
boolean haveFragments = false;
for (int i=0; i<N; i++) {
Fragment f = mActive.get(i);
if (f != null) {
if (f.mIndex < 0) {
throwException(new IllegalStateException(
"Failure saving state: active " + f
+ " has cleared index: " + f.mIndex));
}
haveFragments = true;
FragmentState fs = new FragmentState(f);
active[i] = fs;
if (f.mState > Fragment.INITIALIZING && fs.mSavedFragmentState == null) {
fs.mSavedFragmentState = saveFragmentBasicState(f);
if (f.mTarget != null) {
if (f.mTarget.mIndex < 0) {
throwException(new IllegalStateException(
"Failure saving state: " + f
+ " has target not in fragment manager: " + f.mTarget));
}
if (fs.mSavedFragmentState == null) {
fs.mSavedFragmentState = new Bundle();
}
putFragment(fs.mSavedFragmentState,
FragmentManagerImpl.TARGET_STATE_TAG, f.mTarget);
if (f.mTargetRequestCode != 0) {
fs.mSavedFragmentState.putInt(
FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG,
f.mTargetRequestCode);
}
}
} else {
fs.mSavedFragmentState = f.mSavedFragmentState;
}
if (DEBUG) Log.v(TAG, "Saved state of " + f + ": "
+ fs.mSavedFragmentState);
}
}
if (!haveFragments) {
if (DEBUG) Log.v(TAG, "saveAllState: no fragments!");
return null;
}
int[] added = null;
BackStackState[] backStack = null;
// Build list of currently added fragments.
if (mAdded != null) {
N = mAdded.size();
if (N > 0) {
added = new int[N];
for (int i=0; i<N; i++) {
added[i] = mAdded.get(i).mIndex;
if (added[i] < 0) {
throwException(new IllegalStateException(
"Failure saving state: active " + mAdded.get(i)
+ " has cleared index: " + added[i]));
}
if (DEBUG) Log.v(TAG, "saveAllState: adding fragment #" + i
+ ": " + mAdded.get(i));
}
}
}
// Now save back stack.
if (mBackStack != null) {
N = mBackStack.size();
if (N > 0) {
backStack = new BackStackState[N];
for (int i=0; i<N; i++) {
backStack[i] = new BackStackState(this, mBackStack.get(i));
if (DEBUG) Log.v(TAG, "saveAllState: adding back stack #" + i
+ ": " + mBackStack.get(i));
}
}
}
FragmentManagerState fms = new FragmentManagerState();
fms.mActive = active;
fms.mAdded = added;
fms.mBackStack = backStack;
return fms;
}
從上面的代碼我們可以看出驻仅,重點(diǎn)是對(duì)mActive的fragment的狀態(tài)保存,調(diào)用的是以下方法:
Bundle saveFragmentBasicState(Fragment f) {
Bundle result = null;
if (mStateBundle == null) {
mStateBundle = new Bundle();
}
// 回調(diào)callback函數(shù)登渣,保存fragment的狀態(tài)
f.performSaveInstanceState(mStateBundle);
if (!mStateBundle.isEmpty()) {
// 確實(shí)有保存的東西則設(shè)置result
result = mStateBundle;
mStateBundle = null;
}
// 保存fragment view樹的狀態(tài)
if (f.mView != null) {
saveFragmentViewState(f);
}
if (f.mSavedViewState != null) {
if (result == null) {
result = new Bundle();
}
// 將view樹的狀態(tài)作為一個(gè)key噪服、value放到result中
result.putSparseParcelableArray(
FragmentManagerImpl.VIEW_STATE_TAG, f.mSavedViewState);
}
if (!f.mUserVisibleHint) {
if (result == null) {
result = new Bundle();
}
// Only add this if it's not the default value
result.putBoolean(FragmentManagerImpl.USER_VISIBLE_HINT_TAG, f.mUserVisibleHint);
}
return result; // 返回最終的result作為fragment保存的狀態(tài)
}
void saveFragmentViewState(Fragment f) {
if (f.mView == null) {
return;
}
if (mStateArray == null) {
mStateArray = new SparseArray<Parcelable>();
} else {
mStateArray.clear();
}
f.mView.saveHierarchyState(mStateArray);
if (mStateArray.size() > 0) {
f.mSavedViewState = mStateArray;
mStateArray = null;
}
}
在繼續(xù)分析之前,我們先來說下源碼里的這2個(gè)字段之間的關(guān)系:
從上面的源碼中我們可以看到mSavedViewState是作為fragment狀態(tài)mSavedFragmentState的一個(gè)key/value對(duì)存在的绍豁,即只是fragment狀態(tài)的一部分芯咧。
文章開頭現(xiàn)象的原因分析
這里的重點(diǎn)還是fragment源碼分析那篇文章中最后提到的FragmentManagerImpl的void moveToState(Fragment f, int newState, int transit, int transitionStyle, boolean keepActive)
方法,這里我們截取關(guān)鍵的片段分析:
- 當(dāng)fragment的狀態(tài)在向前move的時(shí)候,即變大到resume狀態(tài)的過程中敬飒,會(huì)經(jīng)歷這樣的代碼:
f.restoreViewState
的源碼如下:
final void restoreViewState(Bundle savedInstanceState) {
// 第一次show的時(shí)候邪铲,這個(gè)字段為null,所以不會(huì)觸發(fā)
// restoreHierarchyState方法
if (mSavedViewState != null) {
mView.restoreHierarchyState(mSavedViewState);
mSavedViewState = null;
}
mCalled = false;
onViewStateRestored(savedInstanceState);
if (!mCalled) {
throw new SuperNotCalledException("Fragment " + this
+ " did not call through to super.onViewStateRestored()");
}
}
同時(shí)從源碼我們也可以看出无拗,此方法的調(diào)用時(shí)機(jī)是位于onActivityCreate之后带到,onStart之前,但都在onCreateView之后英染,所以如果mView.restoreHierarchyState起作用的話揽惹,那么我們?cè)趏nCreateView里面的設(shè)置自然會(huì)被沖掉。
- 當(dāng)fragment的狀態(tài)在向后move的時(shí)候四康,即變小到INITIALIZING的過程中搪搏,會(huì)經(jīng)歷這樣的代碼:
saveFragmentViewState(f)
方法會(huì)在onStop之后,onDestroyView之前被調(diào)用闪金,這個(gè)方法我們前面介紹過疯溺,經(jīng)過這個(gè)方法之后,f.mSavedViewState
會(huì)被設(shè)置為有效值哎垦。所以在我們前面的例子里囱嫩,當(dāng)下次重用同一個(gè)實(shí)例時(shí),由于f.mSavedViewState
有值(即使fragment執(zhí)行了onDestroyView之類的callback漏设,調(diào)用了其initState方法墨闲,但mSavedViewState并不會(huì)被重置),所以就發(fā)生了前面截圖里的調(diào)用棧了郑口,即mView.restoreHierarchyState被調(diào)用了鸳碧。而我們知道checkbox關(guān)于狀態(tài)恢復(fù)的實(shí)現(xiàn)正好就是調(diào)用setChecked方法(在我們的案例里有多個(gè)checkbox,但只有1個(gè)是選中了潘酗,后面的所有都是沒選中的杆兵,再加上是從同一個(gè)layout文件中inflate出來的,大家都有相同的id仔夺,在保存的過程中后面的checkbox狀態(tài)就覆蓋了前面選中了的checkbox狀態(tài))琐脏,至此為止,真相大白缸兔,一切都解釋通了日裙。
總結(jié)
fragment的狀態(tài)保存、恢復(fù)和f.mView的狀態(tài)保存惰蜜、恢復(fù)發(fā)生的時(shí)機(jī)不一樣昂拂,這兩者不能等同;
fragment的狀態(tài)保存總體上是跟隨外部的act的狀態(tài)保存抛猖,當(dāng)fragment保存狀態(tài)時(shí)會(huì)觸發(fā)f.mView的保存格侯;fragment狀態(tài)的恢復(fù)大體上是重建其mActive鼻听、mAdded、mBackStack這3個(gè)字段联四,時(shí)機(jī)是在外部act.onCreate里觸發(fā)的撑碴;
f.mView會(huì)在onDestroyView之前自動(dòng)保存view樹狀態(tài),并且在(同一個(gè)實(shí)例)onActivityCreate之后朝墩、onStart之前自動(dòng)恢復(fù)之前保存的狀態(tài)醉拓;
盡量別重用fragment實(shí)例,fragment最好不要有自己的字段收苏,需要的參數(shù)可以通過
setArguments(Bundle args)
這樣的方法傳遞進(jìn)去亿卤;如果遇上會(huì)產(chǎn)生相同view id的布局時(shí),要特別關(guān)注下狀態(tài)保存鹿霸、恢復(fù)這方面排吴,看看是否正常work,多實(shí)際測試杜跷;