1. 程式人生 > >fragment中add與replace的區別

fragment中add與replace的區別

使用 FragmentTransaction 的時候,它提供了這樣兩個方法,一個 add , 一個 replace ,對這兩個方法的區別一直有點疑惑。我覺得使用 add 的話,在按返回鍵應該是回退到上一個 Fragment,而使用 replace 的話,那個別 replace 的就已經不存在了,所以就不會回退了。但事實不是這樣子的。add 和 replace 影響的只是介面,而控制回退的,是事務。

public abstract FragmentTransaction add (int containerViewId, Fragment fragment, String tag)

Add a fragment to the activity state. This fragment may optionally also have its view (if Fragment.onCreateView returns non-null) into a container view of the activity.

add 是把一個fragment新增到一個容器 container 裡。

public abstract FragmentTransaction replace (int containerViewId, Fragment fragment, String tag)

Replace an existing fragment that was added to a container. This is essentially the same as calling remove(Fragment) for all currently added fragments that were added with the same containerViewId and then add(int, Fragment, String) with the same arguments given here.

replace 是先remove掉相同id的所有fragment,然後在add當前的這個fragment。

在大部分情況下,這兩個的表現基本相同。因為,一般,咱們會使用一個FrameLayout來當容器,而每個Fragment被add 或者 replace 到這個FrameLayout的時候,都是顯示在最上層的。所以你看到的介面都是一樣的。但是,使用add的情況下,這個FrameLayout其實有2層,多層肯定要比一層的來得浪費,所以還是推薦使用replace。當然有時候還是需要使用add的。比如要實現輪播圖的效果,每個輪播圖都是一個獨立的Fragment,而他的容器FrameLayout需要add多個Fragment,這樣他就可以根據提供的邏輯進行輪播了。

而至於返回鍵的時候,這個跟事務有關,跟使用add還是replace沒有任何關係。

2015.08.04 更新。

發現這篇博文被搜尋得挺多的,上面是分析是在官方文件上的基礎上加上一些個人的猜測,為了避免誤人子弟,下面從程式碼實現的角度做了些分析。希望能幫到大家,也煩請大家在轉載的同時註明出處,畢竟寫這麼一篇博文確實很不容易(binkery)。

FragmentManager 是一個抽象類,實現類是 FragmentManagerImpl ,跟 FragmentManager 在同一個類檔案裡。FragmentTransaction 也是一個抽象類,具體實現是 BackStackRecord 。BackStackRecord 其實是一個封裝了一個佇列。咱們看 add 方法和 replace 方法。

add 方法和 replace 方法都是把一個操作 OP_XX 放入到佇列裡,Op 是其內部封裝的一個操作的類。在 BackStackRecord 的 run 方法裡,每次會從佇列的頭(mHead)獲取一個操作 Op ,如果 Op 操作是 add ,則呼叫 FragmentManager 的 addFragment() 方法,如果 Op 操作是 replace ,則先呼叫 FragmentManager 的 removeFragment() 方法,然後再呼叫 addFragment() 方法。

下面是 add 方法。

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

下面是 replace 方法。

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;
}

add 和 replace 方法都是呼叫的 doAddOp 方法。也就是把一個操作 Op 新增到佇列。

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);
}

run 方法才是真正執行的方法。什麼時候執行先不考慮,只需要知道一系列的操作會一次執行,而不是一個操作執行一次。
run 方法有點大,就看一下 while 迴圈開始和結束的時候,以及 switch case 裡 OP_ADD 和 OP_REPLACE 分支就可以了。

public void run() {
    if (FragmentManagerImpl.DEBUG) {
        Log.v(TAG, "Run: " + this);
    }

    if (mAddToBackStack) {
        if (mIndex < 0) {
            throw new IllegalStateException("addToBackStack() called after commit()");
        }
    }

    bumpBackStackNesting(1);

    SparseArray<Fragment> firstOutFragments = new SparseArray<Fragment>();
    SparseArray<Fragment> lastInFragments = new SparseArray<Fragment>();
    calculateFragments(firstOutFragments, lastInFragments);
    beginTransition(firstOutFragments, lastInFragments, false);
    // 獲取佇列的頭
    Op op = mHead;
    while (op != null) {
        switch (op.cmd) {
            case OP_ADD: {
                Fragment f = op.fragment;
                f.mNextAnim = op.enterAnim;
                mManager.addFragment(f, false);//新增
            }
            break;
            case OP_REPLACE: {
                Fragment f = op.fragment;
                if (mManager.mAdded != null) {
                    for (int i = 0; i < mManager.mAdded.size(); i++) {
                        Fragment old = mManager.mAdded.get(i);
                        if (FragmentManagerImpl.DEBUG) {
                            Log.v(TAG,
                                    "OP_REPLACE: adding=" + f + " old=" + old);
                        }
                        if (f == null || old.mContainerId == f.mContainerId) {
                            if (old == f) {
                                op.fragment = f = null;
                            } else {
                                if (op.removed == null) {
                                    op.removed = new ArrayList<Fragment>();
                                }
                                op.removed.add(old);
                                old.mNextAnim = op.exitAnim;
                                if (mAddToBackStack) {
                                    old.mBackStackNesting += 1;
                                    if (FragmentManagerImpl.DEBUG) {
                                        Log.v(TAG, "Bump nesting of "
                                                + old + " to " + old.mBackStackNesting);
                                    }
                                }
                                mManager.removeFragment(old, mTransition, mTransitionStyle);//刪除
                            }
                        }
                    }
                }
                if (f != null) {
                    f.mNextAnim = op.enterAnim;
                    mManager.addFragment(f, false);//新增
                }
            }
            break;
            case OP_REMOVE: {
                Fragment f = op.fragment;
                f.mNextAnim = op.exitAnim;
                mManager.removeFragment(f, mTransition, mTransitionStyle);
            }
            break;
            case OP_HIDE: {
                Fragment f = op.fragment;
                f.mNextAnim = op.exitAnim;
                mManager.hideFragment(f, mTransition, mTransitionStyle);
            }
            break;
            case OP_SHOW: {
                Fragment f = op.fragment;
                f.mNextAnim = op.enterAnim;
                mManager.showFragment(f, mTransition, mTransitionStyle);
            }
            break;
            case OP_DETACH: {
                Fragment f = op.fragment;
                f.mNextAnim = op.exitAnim;
                mManager.detachFragment(f, mTransition, mTransitionStyle);
            }
            break;
            case OP_ATTACH: {
                Fragment f = op.fragment;
                f.mNextAnim = op.enterAnim;
                mManager.attachFragment(f, mTransition, mTransitionStyle);
            }
            break;
            default: {
                throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
            }
        }

        op = op.next;//佇列的下一個
    }

    mManager.moveToState(mManager.mCurState, mTransition,
            mTransitionStyle, true);

    if (mAddToBackStack) {
        mManager.addBackStackState(this);
    }
}

BackStackRecord 的構造器裡引數列表裡有一個 FragmentManager ,所有 BackStackRecord 其實是有一個 FragmentManager 的引用的,BackStackRecord 可以直接呼叫 FragmentManager 的 addFragment 方法。
下面是 FragmentManager 的 addFragment() 方法,每次 add 一個 Fragment,Fragment 物件都會被放入到 mAdded 的容器裡。

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);
        }
    }
}

有時候,咱們 add Fragment A, 然後 add Fragment B,B 把 A 都覆蓋了,點選選單的時候 A 和 B 的選單選項都出來了,這是為什麼?原因在下面。當在建立 OptionsMenu 的時候,FragmentManager 遍歷了 mAdded 容器,所以 A 和 B 的選單都被新增進來了。也就是說使用 add 的方式,雖然 B 把 A 覆蓋住了,但是 A 還是存活的,而且是活動著的。

public boolean dispatchCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    boolean show = false;
    ArrayList<Fragment> newMenus = null;
    if (mAdded != null) {
        for (int i=0; i<mAdded.size(); i++) {
            Fragment f = mAdded.get(i);
            if (f != null) {
                if (f.performCreateOptionsMenu(menu, inflater)) {
                    show = true;
                    if (newMenus == null) {
                        newMenus = new ArrayList<Fragment>();
                    }
                    newMenus.add(f);
                }
            }
        }
    }
    
    if (mCreatedMenus != null) {
        for (int i=0; i<mCreatedMenus.size(); i++) {
            Fragment f = mCreatedMenus.get(i);
            if (newMenus == null || !newMenus.contains(f)) {
                f.onDestroyOptionsMenu();
            }
        }
    }
    
    mCreatedMenus = newMenus;
    
    return show;
}