完美解決Fragment重疊的問題
一 原因
app所配置設定的記憶體不足時,非可見Activity都有可能被回收,FragmentActivity回收表現在:當FragmentActivity進入背景有可能被系統回收時,系統會回調Activity的
/**
* Save all appropriate fragment state.
*/
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
if (mPendingFragmentActivityResults.size() > 0) {
outState.putInt(NEXT_CANDIDATE_REQUEST_INDEX_TAG, mNextCandidateRequestIndex);
int[] requestCodes = new int[mPendingFragmentActivityResults.size()];
String[] fragmentWhos = new String[mPendingFragmentActivityResults.size()];
for (int i = 0; i < mPendingFragmentActivityResults.size(); i++) {
requestCodes[i] = mPendingFragmentActivityResults.keyAt(i);
fragmentWhos[i] = mPendingFragmentActivityResults.valueAt(i);
}
outState.putIntArray(ALLOCATED_REQUEST_INDICIES_TAG, requestCodes);
outState.putStringArray(REQUEST_FRAGMENT_WHO_TAG, fragmentWhos);
}
}
生命周期方法。很明顯FragmentActivity儲存了之前所有的Fragment狀态并且序列化(注意:這裡隻儲存了所有fragment的狀态,并不是儲存目前執行個體,目前的fragment執行個體會随着系統的回收而一起回收)
當系統檢測到該FragmentActivity需要回收時,再次進入該Activity就會重新執行個體化FragmentActivity,并帶入上次onSaveInstance儲存的所有狀态,其中就包括所有的Fragment,而以這種情況執行個體化的FragmentActivity在onCreate的時候會自動處理儲存下來的fragments,請看以下FragmentActivity的源碼
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
mFragments.attachHost(null /*parent*/);
super.onCreate(savedInstanceState);
NonConfigurationInstances nc =
(NonConfigurationInstances) getLastNonConfigurationInstance();
if (nc != null) {
mFragments.restoreLoaderNonConfig(nc.loaders);
}
if (savedInstanceState != null) {
Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
// Check if there are any pending onActivityResult calls to descendent Fragments.
if (savedInstanceState.containsKey(NEXT_CANDIDATE_REQUEST_INDEX_TAG)) {
mNextCandidateRequestIndex =
savedInstanceState.getInt(NEXT_CANDIDATE_REQUEST_INDEX_TAG);
int[] requestCodes = savedInstanceState.getIntArray(ALLOCATED_REQUEST_INDICIES_TAG);
String[] fragmentWhos = savedInstanceState.getStringArray(REQUEST_FRAGMENT_WHO_TAG);
if (requestCodes == null || fragmentWhos == null ||
requestCodes.length != fragmentWhos.length) {
Log.w(TAG, "Invalid requestCode mapping in savedInstanceState.");
} else {
mPendingFragmentActivityResults = new SparseArrayCompat<>(requestCodes.length);
for (int i = 0; i < requestCodes.length; i++) {
mPendingFragmentActivityResults.put(requestCodes[i], fragmentWhos[i]);
}
}
}
}
if (mPendingFragmentActivityResults == null) {
mPendingFragmentActivityResults = new SparseArrayCompat<>();
mNextCandidateRequestIndex = 0;
}
mFragments.dispatchCreate();
}
注意:不同版本的源碼大同小異。 但是,一般情況下我們在建立FragmentActivity的時候會手動建立Fragment執行個體并add到Activity上,那麼問題就處在這裡,這時候系統自己生成了一套Fragmemts自己又建立了一套Fragments。而我們在代碼中隻操作了自己建立的那些Fragments,進而導緻界面出現重疊的情況。
二 解決問題
既然系統根據之前的fragments狀态恢複到新的Activity執行個體中,那麼本着不産生新問題以及不破壞Activity生命周期的原則(有人說注釋掉onSaveInstance方法),且看我如何完美解決此問題:
正常建立Activity時,我們将fragments儲存起來,以便代碼友善使用
private BaseFragment[] fragments;
也許你的是ArrayList,SparseList都可以(對于fragments量小,基本隻有索引查找時建議使用數組形式,減少開銷)
以下是我如何動态建立fragment以及如何hide和show,其中enum類型的Item你們可以了解成一個int型的index即可,我自己封裝了一些業務需要,
public void showFragment(Item item) {
if (item == null || activity.getCurrentItem() == item) {
return;
}
hideAll();
FragmentTransaction transaction = manager.beginTransaction();
if (fragments[item.getIndex()] == null) {
switch (item) {
case HOME://首頁
fragments[item.getIndex()] = new HomeFragment();
break;
case USER_CENTER://使用者中心
fragments[item.getIndex()] = new UserCenterFragment();
break;
default:
break;
}
transaction.add(R.id.container, fragments[item.getIndex()], String.valueOf(item.getIndex()));
} else {
transaction.show(fragments[item.getIndex()]);
}
transaction.commitAllowingStateLoss();
}
這是正常情況下的建立,那麼針對系統回收的情況我們該怎麼處理呢?簡單!!!!!! 我們隻需要将系統建立的Fragments存儲到fragments數組中即可,當代碼走到showFragment()方法的時候,就不會再次建立Fragment執行個體了。
直接上代碼:(請注意,這個方法是我自己寫的,不是系統方法)最終這個方法應該在Activity的onCreate方法中調用
public void onRestoreInstanceState(Bundle savedInstanceState) {
if (savedInstanceState != null) {
Item currentItem = null;
int index = savedInstanceState.getInt(MainActivity.ITEM_INDEX_KEY, Item.HOME.getIndex());
for (int i = 0; i < fragments.length; i++) {
int tmpIndex = Item.values()[i].getIndex();
fragments[i] = (BaseFragment) manager.findFragmentByTag(String.valueOf(tmpIndex));
if (index == tmpIndex) {
currentItem = Item.values()[i];
}
}
if (currentItem != null) {
activity.setCurrentItem(currentItem);
} else {
activity.setCurrentItem(Item.HOME);
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_main);
//注意此代碼必須在super.onCreate之後,因為必須保證父類FragmentActivity的onCreate優先執行,
if (savedInstanceState != null) {
containerModel.onRestoreInstanceState(savedInstanceState);
} else {
navigationView.setCurrentItem(Item.HOME);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_main);
//注意此代碼必須在super.onCreate之後,因為必須保證父類FragmentActivity的onCreate優先執行,
if (savedInstanceState != null) {
containerModel.onRestoreInstanceState(savedInstanceState);
} else {
navigationView.setCurrentItem(Item.HOME);
}
}
好了 問題解決!!
附:我在onRestoreInstanceState中儲存了一個目前Fragment的索引,也是為了解決,當重新進來時每次都展示第一個fragment的問題,這樣使用者就察覺不到背景系統回收每次都回到首頁的問題。索引的儲存請看:
@Override
protected void onSaveInstanceState(Bundle outState) {
Item item = getCurrentItem();
if (item != null) {
outState.putInt(ITEM_INDEX_KEY, item.getIndex());
}
super.onSaveInstanceState(outState);
}