1. 程式人生 > >Android進階——Fragment詳解之操作原理(三)

Android進階——Fragment詳解之操作原理(三)

引言

前一篇文章總結了Fragment 的基本概念和基本的用法,相信大家也能夠掌握一些知識了,但是對於一些操作可能還是不知其所以然,說實話曾經很長一段時間為也是暈乎乎的,後來才慢慢重視去學習瞭解,才略知一二,遂分享之。

一、管理Fragement所涉及到的關鍵類

應用Fragment的過程中涉及到的關鍵類主要有:FragmentManager和、FragmentManagerImplFragmentTransaction和BackStackRecord等。

二、Fragment操作原理詳述

1、FragmentManager和FragmentManagerImpl

FragmentManager

是一個抽象類,定了獲取指定Fragement物件findFragmentById(id)findFragmentByTag(tag)、從後臺棧中彈出(模擬使用者按下BACK鍵)popBackStack(),註冊監聽addOnBackStackChangeListner和移除監聽removeOnBackStackChangedListener以及開啟事務的方法beginTransaction,但直接與Activity互動的並承擔實際Fragment管理工作的是FragmentManagerImpl。他們的部分原始碼結構【FragmentManager.java (frameworks\base\core\java\android\app)
】如下:

public abstract class FragmentManager {

    public interface BackStackEntry {
        public int getId();
        public String getName();
        ...
    }
    /**
     * Interface to watch for changes to the back stack.
     */
    public interface OnBackStackChangedListener {
        /**
         * Called whenever the contents of the back stack change.
         */
public void onBackStackChanged(); } public abstract FragmentTransaction beginTransaction(); public abstract Fragment findFragmentById(int id); public abstract Fragment findFragmentByTag(String tag); public static final int POP_BACK_STACK_INCLUSIVE = 1<<0; public abstract void popBackStack(); public abstract void popBackStack(String name, int flags); public abstract void popBackStack(int id, int flags); public abstract void addOnBackStackChangedListener(OnBackStackChangedListener listener); public abstract void removeOnBackStackChangedListener(OnBackStackChangedListener listener); public abstract Fragment getFragment(Bundle bundle, String key); public abstract Fragment.SavedState saveFragmentInstanceState(Fragment f); } /** * Container for fragments associated with an activity. */ final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory2 { ArrayList<BackStackRecord> mBackStackIndices;//BackStackRecord實現了FragmentTransaction static class AnimateOnHWLayerIfNeededListener implements Animator.AnimatorListener { private boolean mShouldRunOnHWLayer = false; private View mView; public AnimateOnHWLayerIfNeededListener(final View v) { if (v == null) { return; } mView = v; } @Override public void onAnimationStart(Animator animation) { mShouldRunOnHWLayer = shouldRunOnHWLayer(mView, animation); if (mShouldRunOnHWLayer) { mView.setLayerType(View.LAYER_TYPE_HARDWARE, null); } } @Override public void onAnimationEnd(Animator animation) { if (mShouldRunOnHWLayer) { mView.setLayerType(View.LAYER_TYPE_NONE, null); } mView = null; animation.removeListener(this); } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } } ... @Override public FragmentTransaction beginTransaction() { return new BackStackRecord(this); } @Override public void popBackStack() { enqueueAction(new Runnable() { @Override public void run() { popBackStackState(mHost.getHandler(), null, -1, 0); } }, false); } @Override public void popBackStack(final String name, final int flags) { enqueueAction(new Runnable() { @Override public void run() { popBackStackState(mHost.getHandler(), name, -1, flags); } }, false); } @Override public void addOnBackStackChangedListener(OnBackStackChangedListener listener) { if (mBackStackChangeListeners == null) { mBackStackChangeListeners = new ArrayList<OnBackStackChangedListener>(); } mBackStackChangeListeners.add(listener); } @Override public void removeOnBackStackChangedListener(OnBackStackChangedListener listener) { if (mBackStackChangeListeners != null) { mBackStackChangeListeners.remove(listener); } } Animator loadAnimator(Fragment fragment, int transit, boolean enter, int transitionStyle) { Animator animObj = fragment.onCreateAnimator(transit, enter, fragment.mNextAnim); if (animObj != null) { return animObj; } if (fragment.mNextAnim != 0) { Animator anim = AnimatorInflater.loadAnimator(mHost.getContext(), fragment.mNextAnim); if (anim != null) { return anim; } } if (transit == 0) { return null; } int styleIndex = transitToStyleIndex(transit, enter); if (styleIndex < 0) { return null; } if (transitionStyle == 0 && mHost.onHasWindowAnimations()) { transitionStyle = mHost.onGetWindowAnimations(); } if (transitionStyle == 0) { return null; } TypedArray attrs = mHost.getContext().obtainStyledAttributes(transitionStyle, com.android.internal.R.styleable.FragmentAnimation); int anim = attrs.getResourceId(styleIndex, 0); attrs.recycle(); if (anim == 0) { return null; } return AnimatorInflater.loadAnimator(mHost.getContext(), anim); } public void addFragment(Fragment fragment, boolean moveToStateNow) { if (mAdded == null) { mAdded = new ArrayList<Fragment>(); } if (DEBUG) Log.v(TAG, "add: " + fragment); makeActive(fragment); 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); } } } public void removeFragment(Fragment fragment, int transition, int transitionStyle) { if (DEBUG) Log.v(TAG, "remove: " + fragment + " nesting=" + fragment.mBackStackNesting); final boolean inactive = !fragment.isInBackStack(); if (!fragment.mDetached || inactive) { if (false) { // Would be nice to catch a bad remove here, but we need // time to test this to make sure we aren't crashes cases // where it is not a problem. if (!mAdded.contains(fragment)) { throw new IllegalStateException("Fragment not added: " + fragment); } } if (mAdded != null) { mAdded.remove(fragment); } if (fragment.mHasMenu && fragment.mMenuVisible) { mNeedMenuInvalidate = true; } fragment.mAdded = false; fragment.mRemoving = true; moveToState(fragment, inactive ? Fragment.INITIALIZING : Fragment.CREATED, transition, transitionStyle, false); } } public void hideFragment(Fragment fragment, int transition, int transitionStyle) { if (DEBUG) Log.v(TAG, "hide: " + fragment); if (!fragment.mHidden) { // If there is a showing or hidding animation, stop it immediately. if (fragment.mAnimatingShowHide != null) { fragment.mAnimatingShowHide.end(); } fragment.mHidden = true; if (fragment.mView != null) { Animator anim = loadAnimator(fragment, transition, false, transitionStyle); if (anim != null) { fragment.mAnimatingShowHide = anim; anim.setTarget(fragment.mView); // Delay the actual hide operation until the animation finishes, otherwise // the fragment will just immediately disappear final Fragment finalFragment = fragment; anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { finalFragment.mAnimatingShowHide = null; if (finalFragment.mView != null) { finalFragment.mView.setVisibility(View.GONE); } } }); setHWLayerAnimListenerIfAlpha(finalFragment.mView, anim); anim.start(); } else { fragment.mView.setVisibility(View.GONE); } } if (fragment.mAdded && fragment.mHasMenu && fragment.mMenuVisible) { mNeedMenuInvalidate = true; } fragment.onHiddenChanged(true); } } public void showFragment(Fragment fragment, int transition, int transitionStyle) { if (DEBUG) Log.v(TAG, "show: " + fragment); if (fragment.mHidden) { // If there is a showing or hidding animation, stop it immediately. if (fragment.mAnimatingShowHide != null) { fragment.mAnimatingShowHide.end(); } fragment.mHidden = false; if (fragment.mView != null) { Animator anim = loadAnimator(fragment, transition, true, transitionStyle); if (anim != null) { fragment.mAnimatingShowHide = anim; anim.setTarget(fragment.mView); setHWLayerAnimListenerIfAlpha(fragment.mView, anim); final Fragment finalFragment = fragment; anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { finalFragment.mAnimatingShowHide = null; } }); anim.start(); } fragment.mView.setVisibility(View.VISIBLE); } if (fragment.mAdded && fragment.mHasMenu && fragment.mMenuVisible) { mNeedMenuInvalidate = true; } fragment.onHiddenChanged(false); } } public void detachFragment(Fragment fragment, int transition, int transitionStyle) { if (DEBUG) Log.v(TAG, "detach: " + fragment); if (!fragment.mDetached) { fragment.mDetached = true; if (fragment.mAdded) { // We are not already in back stack, so need to remove the fragment. if (mAdded != null) { if (DEBUG) Log.v(TAG, "remove from detach: " + fragment); mAdded.remove(fragment); } if (fragment.mHasMenu && fragment.mMenuVisible) { mNeedMenuInvalidate = true; } fragment.mAdded = false; moveToState(fragment, Fragment.CREATED, transition, transitionStyle, false); } } } public void attachFragment(Fragment fragment, int transition, int transitionStyle) { if (DEBUG) Log.v(TAG, "attach: " + fragment); if (fragment.mDetached) { fragment.mDetached = false; if (!fragment.mAdded) { if (mAdded == null) { mAdded = new ArrayList<Fragment>(); } if (mAdded.contains(fragment)) { throw new IllegalStateException("Fragment already added: " + fragment); } if (DEBUG) Log.v(TAG, "add from attach: " + fragment); mAdded.add(fragment); fragment.mAdded = true; if (fragment.mHasMenu && fragment.mMenuVisible) { mNeedMenuInvalidate = true; } moveToState(fragment, mCurState, transition, transitionStyle, false); } } } public Fragment findFragmentById(int id) { if (mAdded != null) { // First look through added fragments. for (int i=mAdded.size()-1; i>=0; i--) { Fragment f = mAdded.get(i); if (f != null && f.mFragmentId == id) { return f; } } } if (mActive != null) { // Now for any known fragment. for (int i=mActive.size()-1; i>=0; i--) { Fragment f = mActive.get(i); if (f != null && f.mFragmentId == id) { return f; } } } return null; } public Fragment findFragmentByTag(String tag) { if (mAdded != null && tag != null) { // First look through added fragments. for (int i=mAdded.size()-1; i>=0; i--) { Fragment f = mAdded.get(i); if (f != null && tag.equals(f.mTag)) { return f; } } } if (mActive != null && tag != null) { // Now for any known fragment. for (int i=mActive.size()-1; i>=0; i--) { Fragment f = mActive.get(i); if (f != null && tag.equals(f.mTag)) { return f; } } } return null; } void reportBackStackChanged() { if (mBackStackChangeListeners != null) { for (int i=0; i<mBackStackChangeListeners.size(); i++) { mBackStackChangeListeners.get(i).onBackStackChanged(); } } } void addBackStackState(BackStackRecord state) { if (mBackStack == null) { mBackStack = new ArrayList<BackStackRecord>(); } mBackStack.add(state); reportBackStackChanged(); } @Override public View onCreateView(View parent, String name, Context context, AttributeSet attrs) { if (!"fragment".equals(name)) { return null; } String fname = attrs.getAttributeValue(null, "class"); TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Fragment); if (fname == null) { fname = a.getString(com.android.internal.R.styleable.Fragment_name); } int id = a.getResourceId(com.android.internal.R.styleable.Fragment_id, View.NO_ID); String tag = a.getString(com.android.internal.R.styleable.Fragment_tag); a.recycle(); int containerId = parent != null ? parent.getId() : 0; if (containerId == View.NO_ID && id == View.NO_ID && tag == null) { throw new IllegalArgumentException(attrs.getPositionDescription() + ": Must specify unique android:id, android:tag, or have a parent with" + " an id for " + fname); } // If we restored from a previous state, we may already have // instantiated this fragment from the state and should use // that instance instead of making a new one. Fragment fragment = id != View.NO_ID ? findFragmentById(id) : null; if (fragment == null && tag != null) { fragment = findFragmentByTag(tag); } if (fragment == null && containerId != View.NO_ID) { fragment = findFragmentById(containerId); } if (FragmentManagerImpl.DEBUG) Log.v(TAG, "onCreateView: id=0x" + Integer.toHexString(id) + " fname=" + fname + " existing=" + fragment); if (fragment == null) { fragment = Fragment.instantiate(context, fname); fragment.mFromLayout = true; fragment.mFragmentId = id != 0 ? id : containerId; fragment.mContainerId = containerId; fragment.mTag = tag; fragment.mInLayout = true; fragment.mFragmentManager = this; fragment.mHost = mHost; fragment.onInflate(mHost.getContext(), attrs, fragment.mSavedFragmentState); addFragment(fragment, true); } else if (fragment.mInLayout) { // A fragment already exists and it is not one we restored from // previous state. throw new IllegalArgumentException(attrs.getPositionDescription() + ": Duplicate id 0x" + Integer.toHexString(id) + ", tag " + tag + ", or parent id 0x" + Integer.toHexString(containerId) + " with another fragment for " + fname); } else { // This fragment was retained from a previous instance; get it // going now. fragment.mInLayout = true; // If this fragment is newly instantiated (either right now, or // from last saved state), then give it the attributes to // initialize itself. if (!fragment.mRetaining) { fragment.onInflate(mHost.getContext(), attrs, fragment.mSavedFragmentState); } } // If we haven't finished entering the CREATED state ourselves yet, // push the inflated child fragment along. if (mCurState < Fragment.CREATED && fragment.mFromLayout) { moveToState(fragment, Fragment.CREATED, 0, 0, false); } else { moveToState(fragment); } if (fragment.mView == null) { throw new IllegalStateException("Fragment " + fname + " did not create a view."); } if (id != 0) { fragment.mView.setId(id); } if (fragment.mView.getTag() == null) { fragment.mView.setTag(tag); } return fragment.mView; } @Override public View onCreateView(String name, Context context, AttributeSet attrs) { return null; } LayoutInflater.Factory2 getLayoutInflaterFactory() { return this; } }

2、FragmentTransaction和BackStackRecord

Android對於Fragment的操作管理,不是針對於某一次的操作,而是類似於Git記錄的是一次改變或者像資料庫的事務一般,記錄的是一系列的add、replace、remove操作集合(這些add等操作都最後都會封裝到一個Op物件裡,Op物件可以看成一個雙向連結串列,記錄了前一個操作和後一個操作,比如說我們add了N個MainFragment後,這N個操作會由系統封裝成N個Op並存到這個對應的BackStackRecord),FragmentTransaction和BackStackRecord(實際是一個實現了FragmentTransaction的類)則是用於管理Fragment事務的業務類,部分原始碼結構如下:

final class BackStackState implements Parcelable {
    public BackStackState(FragmentManagerImpl fm, BackStackRecord bse) {
        int numRemoved = 0;
        BackStackRecord.Op op = bse.mHead;
        while (op != null) {
            if (op.removed != null) {
                numRemoved += op.removed.size();
            }
            op = op.next;
        }
        mOps = new int[bse.mNumOp * 7 + numRemoved];

        if (!bse.mAddToBackStack) {
            throw new IllegalStateException("Not on back stack");
        }

        op = bse.mHead;
        int pos = 0;
        while (op != null) {
            mOps[pos++] = op.cmd;
            mOps[pos++] = op.fragment != null ? op.fragment.mIndex : -1;
            mOps[pos++] = op.enterAnim;
            mOps[pos++] = op.exitAnim;
            mOps[pos++] = op.popEnterAnim;
            mOps[pos++] = op.popExitAnim;
            if (op.removed != null) {
                final int N = op.removed.size();
                mOps[pos++] = N;
                for (int i = 0; i < N; i++) {
                    mOps[pos++] = op.removed.get(i).mIndex;
                }
            } else {
                mOps[pos++] = 0;
            }
            op = op.next;
        }
        mTransition = bse.mTransition;
        mTransitionStyle = bse.mTransitionStyle;
        mName = bse.mName;
        mIndex = bse.mIndex;
        mBreadCrumbTitleRes = bse.mBreadCrumbTitleRes;
        mBreadCrumbTitleText = bse.mBreadCrumbTitleText;
        mBreadCrumbShortTitleRes = bse.mBreadCrumbShortTitleRes;
        mBreadCrumbShortTitleText = bse.mBreadCrumbShortTitleText;
        mSharedElementSourceNames = bse.mSharedElementSourceNames;
        mSharedElementTargetNames = bse.mSharedElementTargetNames;
    }

    public BackStackState(Parcel in) {
        mOps = in.createIntArray();
        mTransition = in.readInt();
        mTransitionStyle = in.readInt();
        mName = in.readString();
        mIndex = in.readInt();
        mBreadCrumbTitleRes = in.readInt();
        mBreadCrumbTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
        mBreadCrumbShortTitleRes = in.readInt();
        mBreadCrumbShortTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
        mSharedElementSourceNames = in.createStringArrayList();
        mSharedElementTargetNames = in.createStringArrayList();
    }
}

/**
 * @hide Entry of an operation on the fragment back stack.
 */
final class BackStackRecord extends FragmentTransaction implements
        FragmentManager.BackStackEntry, Runnable {
    static final String TAG = FragmentManagerImpl.TAG;
    final FragmentManagerImpl mManager;
    static final class Op {
        Op next;
        Op prev;
        int cmd;
        Fragment fragment;
        int enterAnim;
        int exitAnim;
        int popEnterAnim;
        int popExitAnim;
        ArrayList<Fragment> removed;
    }

    Op mHead;
    Op mTail;
    public BackStackRecord(FragmentManagerImpl manager) {
        mManager = manager;
    }
    void addOp(Op op) {
        if (mHead == null) {
            mHead = mTail = op;
        } else {
            op.prev = mTail;
            mTail.next = op;
            mTail = op;
        }
        op.enterAnim = mEnterAnim;
        op.exitAnim = mExitAnim;
        op.popEnterAnim = mPopEnterAnim;
        op.popExitAnim = mPopExitAnim;
        mNumOp++;
    }

    public FragmentTransaction add(Fragment fragment, String tag) {
        doAddOp(0, fragment, tag, OP_ADD);
        return this;
    }

    public FragmentTransaction add(int containerViewId, Fragment fragment) {
        doAddOp(containerViewId, fragment, null, OP_ADD);
        return this;
    }

    public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {
        doAddOp(containerViewId, fragment, tag, OP_ADD);
        return this;
    }

    private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
        fragment.mFragmentManager = mManager;

        if (tag != null) {
            if (fragment.mTag != null && !tag.equals(fragment.mTag)) {
                throw new IllegalStateException("Can't change tag of fragment "
                        + fragment + ": was " + fragment.mTag
                        + " now " + tag);
            }
            fragment.mTag = tag;
        }

        if (containerViewId != 0) {
            if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
                throw new IllegalStateException("Can't change container ID of fragment "
                        + fragment + ": was " + fragment.mFragmentId
                        + " now " + containerViewId);
            }
            fragment.mContainerId = fragment.mFragmentId = containerViewId;
        }

        Op op = new Op();
        op.cmd = opcmd;
        op.fragment = fragment;
        addOp(op);
    }

    public FragmentTransaction replace(int containerViewId, Fragment fragment) {
        return replace(containerViewId, fragment, null);
    }

    public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) {
        if (containerViewId == 0) {
            throw new IllegalArgumentException("Must use non-zero containerViewId");
        }

        doAddOp(containerViewId, fragment, tag, OP_REPLACE);
        return this;
    }

    public FragmentTransaction remove(Fragment fragment) {
        Op op = new Op();
        op.cmd = OP_REMOVE;
        op.fragment = fragment;
        addOp(op);

        return this;
    }

    public FragmentTransaction hide(Fragment fragment) {
        Op op = new Op();
        op.cmd = OP_HIDE;
        op.fragment = fragment;
        addOp(op);

        return this;
    }

    public FragmentTransaction show(Fragment fragment) {
        Op op = new Op();
        op.cmd = OP_SHOW;
        op.fragment = fragment;
        addOp(op);

        return this;
    }

    public FragmentTransaction detach(Fragment fragment) {
        Op op = new Op();
        op.cmd = OP_DETACH;
        op.fragment = fragment;
        addOp(op);

        return this;
    }

    public FragmentTransaction attach(Fragment fragment) {
        Op op = new Op();
        op.cmd = OP_ATTACH;
        op.fragment = fragment;
        addOp(op);
        return this;
    }
    public int commit() {
        return commitInternal(false);
    }
    private TransitionState beginTransition(SparseArray<Fragment> firstOutFragments,
            SparseArray<Fragment> lastInFragments, boolean isBack) {
        TransitionState state = new TransitionState();

        // Adding a non-existent target view makes sure that the transitions don't target
        // any views by default. They'll only target the views we tell add. If we don't
        // add any, then no views will be targeted.
        state.nonExistentView = new View(mManager.mHost.getContext());

        // Go over all leaving fragments.
        for (int i = 0; i < firstOutFragments.size(); i++) {
            int containerId = firstOutFragments.keyAt(i);
            configureTransitions(containerId, state, isBack, firstOutFragments,
                    lastInFragments);
        }

        // Now go over all entering fragments that didn't have a leaving fragment.
        for (int i = 0; i < lastInFragments.size(); i++) {
            int containerId = lastInFragments.keyAt(i);
            if (firstOutFragments.get(containerId) == null) {
                configureTransitions(containerId, state, isBack, firstOutFragments,
                        lastInFragments);
            }
        }
        return state;
    }
    public class TransitionState {
        public ArrayMap<String, String> nameOverrides = new ArrayMap<String, String>();
        public View enteringEpicenterView;
        public View nonExistentView;
    }
}

3、Fragment的add、replace、remove等操作小結

Fragment的addreplace操作這些都是針對棧頂的Fragment,其中每一次addreplace之後,這些操作都會封裝成Op物件並儲存到BackStackRecord裡,也就說這些操作並未真正的起作用,還得把這些操作集合commit(作用類似資料庫事務的commit)。在成功commit之後FragmentManager才會將此次所有Op放到主執行緒中去按順序執行(主程式去呼叫BackStackRecord的run方法)。

  • FragmentTransaction的add操作的管理類似一個佇列的,在此佇列中,根據add進去的先後順序形成了一個”連結串列“
  • 在同一個容器內多次執行add操作,顯示的總是最後一次add 的Fragment,但其他 Fragment 依然是存在於容器內的
  • remove操作其實相當於把指定的Fragment從“連結串列”中移除,如果移除的是頂層的Fragment那麼顯示的自然是次頂層的Fragment;若移除的是中間層的Fragment,那麼顯示的依然是原來的頂層Fragment。
  • attach和detach:使用attach之後Fragment物件的isAdded()返回的值是true,使用detach之後Fragment物件的isAdded()返回的值是false,
  • hide和show操作其作用就如字面意思一樣,但show操作的不是頂層Fragment是無法顯示出來的,同樣的如果我們hide頂層Fragment則自動顯示次頂層。
  • replace操作,原本按照官方的描述是先把容器內的Fragment清除掉,再新增新的Fragment,但是在測試過程中發現並沒有清除掉,或許是個bug吧。而且在實際使用replace來切換頁面,每次切換的時候,Fragment都需要重新例項化,重新載入一邊資料,很明顯非常消耗資源和效能的。普遍採取的替代方案之一:切換時hide,add另一個Fragement;當再次返回的時候,在hide當前的,show之前被hide的Fragment。(好處之一就是不用重複例項化Fragment)
private void chooseFragement(Fragment currFragement, Fragment targetFragment) {
    if (!to.isAdded()) { /*先判斷是否被add過*/
        transaction.hide(currFragement).add(R.id.id_content_frame, Fragment).commit(); // 隱藏當前的fragment,add下一個到Activity中
    } else {
        transaction.hide(currFragement).show(Fragment).commit(); // 隱藏當前的fragment,顯示下一個
    }
}

4、FragmentTransaction事務相關

與資料庫事務類似,Fragement事務也是支援類似回滾的功能的。

4.1、呼叫FragmentTransaction物件的addToBackStack將事務新增到所謂的Back Stack(此棧由Activity管理),當我們按返回鍵時可以返回到此FragmentTransaction提交之前對應的Fragment狀態。

具體用法就是我們在commit之前,先使用addToBackStack將對應的FragmentTransaction物件新增到回退棧

fragmentManager=getFragmentManager();        fragmentManager.beginTransaction().add(viewGroupId,fragment,tag)
                .addToBackStack(tag).commit();

特別地當你remove一個fragment的時候,如果commit()之前沒有呼叫 addToBackStack(),那個fragment將會是destroyed;如果呼叫了addToBackStack(),這個fragment 會是stopped,可以通過返回鍵來恢復

4.2、FragmentManager物件的popBackStack將指定的操作彈出Back Stack

popBackStack的預設就是將最上層的操作彈出(模擬使用者按下返回鍵),當棧中有多層時,我們也可以通過id或tag標識來指定彈出到的操作所在層。值得注意的是popBackStack針對的是一次操作集合(或者類似Git裡的修改),比如說現在一個容器內add了Fragement1並新增至Back Stack再commit,再接著add Fragment2、Fragment3也新增至Back Stack再commit,最後我們執行popBackStack,那麼容器此時的狀態就是回退到了剛剛add Fragment1並commit成功之後的狀態。(如果add 的順序是Fragment2–>Fragment3,回退時則是Fragment3–>Fragment2) 

/*
*id: 當提交變更時transaction.commit()的返回值。
*name: transaction.addToBackStack(String tag)中的tag值;
*flags:有兩個取值:0或FragmentManager.POP_BACK_STACK_INCLUSIVE;
當取值0時,表示除了引數一指定這一層之上的所有層都退出棧,指定的這一層為棧頂層; 當取值POP_BACK_STACK_INCLUSIVE時,表示連著引數一指定的這一層一起退出棧
*/
abstract void   popBackStack();

abstract void   popBackStack(String name, int flags)

abstract void   popBackStack(int id, int flags)

這個函式是非同步的:它將彈出棧的請求加入佇列,但是這個動作直到應用回到事件迴圈才會執行。所以使用popBackStack()來彈出棧內容的話,呼叫該方法後會將事物操作插入到FragmentManager的操作佇列,只有當輪詢到該事物時才能執行。如果想立即執行事物的話,需要使用下面幾個對應的方法:

popBackStackImmediate()  
popBackStackImmediate(String tag)  
popBackStackImmediate(String tag, int flag)  
popBackStackImmediate(int id, int flag)  

4.3、關於commit

呼叫commit()方法並不能立即執行transaction中包含的操作,commit()方法只是把transaction加入Activity的Main執行緒佇列中。但是,如果覺得有必要的話,可以呼叫executePendingTransactions()方法來立即執行commit()提供的transaction。然而這樣做通常是沒有必要的,除非這個transaction被其他執行緒依賴。還有你只能在Activity儲存它的狀態(當用戶要離開Activity時)之前呼叫commit(),如果在儲存狀態之後呼叫commit(),將會丟擲一個異常。這是因為當Activity再次被恢復時commit之後的狀態將丟失。假如丟失也沒關係,我們可以使用commitAllowingStateLoss()方法。

4.4、關於Back Stack狀態的監聽

通過新增監聽器,就可以在Back Stack狀態改變時,及時監聽到並作相應操作。

abstract void addOnBackStackChangedListener(listener);//新增監聽器  
abstract void removeOnBackStackChangedListener(listener);//移除監聽器  

4.4.1新增監聽

在Acitivty裡為FragmentManager新增一個監聽器,一般是在onCreate方法裡

    private class BackStackChangedListener implements FragmentManager.OnBackStackChangedListener{

        @Override
        public void onBackStackChanged() {

        }
    }
BackStackChangedListener listener=new BackStackChangedListener();
FragmentManager fragmentManager = getFragmentManager();  
fragmentManager.addOnBackStackChangedListener(listener );

4.4.2移除監聽

在Acitivty裡為FragmentManager新增一個監聽器,一般是在onDestory方法裡

 fragmentManager.removeOnBackStackChangedListener(listener);  

一般來說,無論是何種監聽,在Activity或者介面銷燬時,都要記得remove掉,否則會潛藏著OOM的隱患。