Android-Fragment進棧和堆棧

在使用Fragment的時候我們一般會這樣寫:
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.content_view, fragment, fragment.getClass().getName());
// transaction.addToBackStack(null);
transaction.commitAllowingStateLoss();
對於是否要加transaction.addToBackStack(null);也就是將Fragment加入到回退棧。官方的說法是取決於你是否要在回退的時候顯示上一個Fragment。
雖然知道這回事,但是在做項目的時候還是沒有清楚的認識,只是習慣性的加上addToBackStack;查看源碼後才瞭解到該神馬時候加入回退棧。
首先看
void addBackStackState(BackStackRecord state) {
if (mBackStack == null) {
mBackStack = new ArrayList();
}
mBackStack.add(state);
reportBackStackChanged();
}
可以看出,我們並不是將Fragment加入到回退棧,而是加了一個叫BackStackRecord的實例;那這個BackStackRecord到底是什麼,簡單的說一個BackStackRecord記錄了一次操作。
final class BackStackRecord extends FragmentTransaction implements FragmentManager.BackStackEntry, Runnable
backstackRecord繼承了FragmentTransaction抽象類,獲得了諸如add,remove,replace這些控制方法,所以我們控制Fragment時一直使用的getSupportFragmentManager().beginTransaction()其實就是返回一個BackStackRecord實例;
backstackRecord也維護了一個Op對象,Op對象的作用就是記錄一次操作的動作和Fragment引用以及操作使用的動畫;
static final class Op {
Op next;
Op prev;
int cmd;
Fragment fragment;
int enterAnim;
int exitAnim;
int popEnterAnim;
int popExitAnim;
ArrayList removed;
}
最後backstackRecord也實現了Runnable接口,通過commit來啓動自身,在run方法中又根據維護的Op對象進行不同的操作。其實不同的Fragment操作就是在啓動不同的BackStatcRecord線程。
下面我們已一次transaction.add操作爲例:此操作也就是調用BackStackRecord裏的add方法,方法中維護一個Op來保存這次add操作和相應的Fragment;然後我們會調用commit方法來提交操作,實質上是啓動實現了Runnable接口的BackStackRecord自身,在run方法中根據Op執行add分支的操作,這裏面我們會調用FragmentManager的addFragment方法
public void run() {

switch (op.cmd) {
case OP_ADD: {
Fragment f = op.fragment;
f.mNextAnim = op.enterAnim;
mManager.addFragment(f, false);
} break;

mManager.moveToState(mManager.mCurState, mTransition, mTransitionStyle, true);
if (mAddToBackStack) {
mManager.addBackStackState(this);
}
}
注意方法的最後會根據mAddToBackStack標識來判斷是否加入到回退棧。
接下來在FragmentManager的addFragment方法中
public void addFragment(Fragment fragment, boolean moveToStateNow) {
if (mAdded == null) {
mAdded = new ArrayList();
}
if (DEBUG) Log.v(TAG, "add: " + fragment);
makeActive(fragment); //通過此方法將fragment加入到一個mActive列表裏。
if (!fragment.mDetached) {
if (mAdded.contains(fragment)) {
throw new IllegalStateException("Fragment already added: " + fragment);
}
mAdded.add(fragment);
fragment.mAdded = true;
fragment.mRemoving = false;
if (fragment.mHasMenu && fragment.mMenuVisible) {
mNeedMenuInvalidate = true;
}
if (moveToStateNow) {
moveToState(fragment);
}
}
}
像上面註釋裏說的,通過makeActive方法將fragment加入到一個mActive列表。這個列表在後面會用到。但現在先來看看代碼裏用藍色標記的兩個方法,這是兩個方法名相同的重載方法,他們最後都會調用一個非常重要的方法:moveToState
void moveToState(Fragment f, int newState, int transit, int transitionStyle, boolean keepActive) {

if (f.mState < newState) {

switch (f.mState) {
case Fragment.INITIALIZING:

case Fragment.CREATED:
if (newState > Fragment.CREATED) {

}
case Fragment.ACTIVITY_CREATED:
case Fragment.STOPPED:
if (newState > Fragment.STOPPED) {
f.performStart();
}
case Fragment.STARTED:
if (newState > Fragment.STARTED) {

f.performResume();

}
}
} else if (f.mState > newState) {
switch (f.mState) {
case Fragment.RESUMED:
if (newState < Fragment.RESUMED) {

f.performPause();

}
case Fragment.STARTED:
if (newState < Fragment.STARTED) {
f.performStop();
}
case Fragment.STOPPED:
if (newState < Fragment.STOPPED) {
f.performReallyStop();
}
case Fragment.ACTIVITY_CREATED:
if (newState < Fragment.ACTIVITY_CREATED) {

}
case Fragment.CREATED:
if (newState < Fragment.CREATED) {

f.performDestroy();

}
}
}
}

    f.mState = newState;
}               

對於這個方法要說明三點:
第一:方法裏所有的分支只有
static final int INITIALIZING = 0; // Not yet created.
static final int CREATED = 1; // Created.
static final int ACTIVITY_CREATED = 2; // The activity has finished its creation.
static final int STOPPED = 3; // Fully created, not started.
static final int STARTED = 4; // Created and started, not resumed.
static final int RESUMED = 5; // Created started and resumed.
這六種狀態,好像不太夠。其實這很好理解,如果傳來的新狀態比fragment的當前狀態大那就是處於創建過程,如果新狀態比當前狀態小那就是處於關閉過程。閉上眼睛想一想就能轉過彎兒了!!!
第二:這裏面所有的case分支都是沒有break方法的,這樣就能保證傳來一個狀態就能把這個狀態之後的所有操作都執行一遍,例如創建時傳INITIALIZING狀態,就能執行INITIALIZING、CREATED、ACTIVITY_CREATED、STOPPED、STARTED這一流程的代碼,而不需要我們挨個的每個狀態都傳;又例如我們重寫回到fragment要調用start()方法,那只需要傳STOPPED(創建時執行的是onStart)就可以,而不需要再傳STARTED(創建時執行的是onResume)。
第三:代碼中的紅色部分會調用FragmentActivity裏的dispatchActivityXXX 方法,這裏面最終會調用另外一個重要方法,它也叫做moveToState(其實這個方法最終也是會去調用上面的moveToState方法):
void moveToState(int newState, int transit, int transitStyle, boolean always) {

for (int i=0; i<mActive.size(); i++) {
Fragment f = mActive.get(i);
if (f != null) {
moveToState(f, newState, transit, transitStyle, false);
if (f.mLoaderManager != null) {
loadersRunning |= f.mLoaderManager.hasRunningLoaders();
}
}
}

        if (!loadersRunning) {
            startPendingDeferredFragments();
        }
    }
}

這裏面有個for循環,它會根據前面提到的mActive列表來調用存儲fragment的moveToState方法(是上面的那個moveToState)。所以如果我們使用show、hide而不是用add、remove來操作fragment顯示與隱藏的話,就會發現一個問題,假設一個FragmentActivity已經創建了三個fragment並且隱藏,然後它在創建第四個fragment的時候,會發現已經隱藏的三個fragment也都運行了onresume方法。這就是因爲這三個fragment已經加入到mActive中,並且在創建第四個的時候循環調用了他們的resume方法。

現在回到最開始的問題,爲什麼說加入回退棧就可以實現按返回鍵退回到上一個fragment界面:
這就要看FragmentActivity裏面的回退方法了
public void onBackPressed() {
if (!mFragments.popBackStackImmediate()) {
finish();
}
}
關鍵在判斷條件,也就是popBackStackImmediate()方法的實現和他的返回值:
他的返回值是由popBackStackState(mActivity.mHandler, null, -1, 0)提供的(注意參數是固定的)
boolean popBackStackState(Handler handler, String name, int id, int flags) {
if (mBackStack == null) {
return false;
}
if (name == null && id < 0 && (flags&POP_BACK_STACK_INCLUSIVE) == 0) {
int last = mBackStack.size()-1;
if (last < 0) {
return false;
}
final BackStackRecord bss = mBackStack.remove(last);
bss.popFromBackStack(true);
reportBackStackChanged();
} else {

}
return true;
}
注意方法的第一個判斷條件:如果mBackStack == null 就直接return false,這樣就會直接執行FragmentActivity的finishi()方法,這也就是當我們不添加addToBackStack方法時按返回鍵不會返回上一個fragment界面而是直接退出程序的原因了。
若添加了addToBackStack方法,也就是mBackStack != null 的情況下,根據固定的參數會進入藍色代碼段,在這裏取出回退棧列表中的最後一條BackStackReco記錄並執行它的popFromBackStack方法:
在這個方法裏會根據BackStackRecord維護的Op對象來執行相應的操作,以replace操作爲例:
case OP_REPLACE: {
Fragment f = op.fragment;
if (f != null) {
f.mNextAnim = op.popExitAnim;
mManager.removeFragment(f,
FragmentManagerImpl.reverseTransit(mTransition),
mTransitionStyle);
}
if (op.removed != null) {
for (int i=0; i<op.removed.size(); i++) {
Fragment old = op.removed.get(i);
old.mNextAnim = op.popEnterAnim;
mManager.addFragment(old, false);
}
}
} break;
從中可以清除的看出是把Op的當前fragment給remove掉,再把Op保存的old fragment給add上,這樣一來就會顯示上一個界面了。
所以,根據不同的情景,當我們不需要fragment會退到上一個界面或者管理的fragment過多而不想保留BackStackRecord記錄過度使用資源時,就可以加入回退棧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章