當FragmentTransaction在add和replace時,它們之間的區別

前言

我們在使用FragmentTransaction的時候,經常會遇到add,replace這兩個方法。
如下:

        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transition = fragmentManager.beginTransaction();
        FragmentOne fragmentOne = new FragmentOne();
        transition.add(R.id.container_layout,fragmentOne);
//        transition.replace(R.id.container_layout,fragmentOne);
        transition.commit();

在一般情況下,它們之間並沒有什麼區別,都能達到一樣的效果。但是想想,如果真的沒有區別的話,Android就完全沒有必要同時提供這兩個方法了。
我們還是先來看看android中對這兩個方法的說明:

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) inserted into a container view of the activity.
add是把一個fragment添加到activity中的容器container view中。

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.
repalce是先remove掉當前相同containerViewId 的所有fragment,然後在add當前這個fragment。

從上面的說明來看,這兩個的使用效果基本相同的。光從上面幾句話的表述中,也許並不能很好的解釋其中的原理。下面我還是從源碼的角度來進行說明。

源碼分析

FragmentManager是個抽象類,當我們執行getSupportFragmentManager方法時,得到的是它的實現類FragmentManagerImpl。FragmentTransaction同樣是抽象類,它的實現類是BackStackRecord類。從BackStackRecord中,我們可以看到add,replace方法。
add方法:

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

replace方法:

    @Override
    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方法,只是在最後的一個參數不同。

private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
        final Class fragmentClass = fragment.getClass();
        final int modifiers = fragmentClass.getModifiers();
        if (fragmentClass.isAnonymousClass() || !Modifier.isPublic(modifiers)
                || (fragmentClass.isMemberClass() && !Modifier.isStatic(modifiers))) {
            throw new IllegalStateException("Fragment " + fragmentClass.getCanonicalName()
                    + " must be a public static class to be  properly recreated from"
                    + " instance state.");
        }

        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 (containerViewId == View.NO_ID) {
                throw new IllegalArgumentException("Can't add fragment "
                        + fragment + " with tag " + tag + " to container view with no id");
            }
            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);
    }

這個方法裏面,首先判斷傳入的fragment方法是不是合法的。
接着,如果傳入的參數TAG不爲空的話,將它賦值到fragment的mTag中,如:fragment.mTag = tag;
然後判斷containerViewId的合法後,執行賦值:fragment.mContainerId = fragment.mFragmentId = containerViewId;
最後將fragment加入到隊中,調用addOp方法。

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

以上完成了準備階段,上面貌似沒有解決add和replace之間的不同操作,我們繼續看BackStackRecord類,知道原來它繼承了Runable,run方法纔是真正執行的方法。在while循環中的switch case包含了OP_ADD和OP_REPLACE的分支,這不正是剛纔方法doAddOp中傳入的不同參數嗎。所以我們只需要關注這兩個case的分支執行的代碼就行了。

case OP_ADD: {
                    Fragment f = op.fragment;
                    f.mNextAnim = enterAnim;
                    mManager.addFragment(f, false);
                } break;
case OP_REPLACE: {
                    Fragment f = op.fragment;
                    int containerId = f.mContainerId;
                    if (mManager.mAdded != null) {
                        for (int i = mManager.mAdded.size() - 1; i >= 0; i--) {
                            Fragment old = mManager.mAdded.get(i);
                            if (FragmentManagerImpl.DEBUG) Log.v(TAG,
                                    "OP_REPLACE: adding=" + f + " old=" + old);
                            if (old.mContainerId == containerId) {
                                if (old == f) {
                                    op.fragment = f = null;
                                } else {
                                    if (op.removed == null) {
                                        op.removed = new ArrayList<Fragment>();
                                    }
                                    op.removed.add(old);
                                    old.mNextAnim = exitAnim;
                                    if (mAddToBackStack) {
                                        old.mBackStackNesting += 1;
                                        if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of "
                                                + old + " to " + old.mBackStackNesting);
                                    }
                                    mManager.removeFragment(old, transition, transitionStyle);
                                }
                            }
                        }
                    }
                    if (f != null) {
                        f.mNextAnim = enterAnim;
                        mManager.addFragment(f, false);
                    }
                } break;

其中OP_ADD很簡單,僅僅就是將fragment加入隊列中,我們來
看看OP_REPLACE。
這裏面有個for循環,目的是將FragmentManagerImpl中,已經添加的Fragment全部遍歷出來,取出與當前fragment中mContainerId相同的fragment,並且remove掉,最後又重新調用addFragment,將它加入到隊列中來。這時調用的方法與OP_ADD相同。
在BackStackRecord中有FragmentManagerImpl引用,然後在構造器的參數列表中進行賦值初始化。在FragmentManagerImpl中的addFragment方法,用來保存每次add的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);
            }
        }
    }

同樣與之對應的還有一個removeFragment的方法,用來處理remove 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 (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);
        }
    }

總結:
在一般的情況下使用add和replace,並沒有什麼區別,replace會先查找FragmentManager中是否包含相同mContainerId 的fragment,如果有就先remove它,然後再add它。
注意它是根據containerViewId來進行比較的,並不是依據對象是否相等來判斷的。在這一點上還需要注意區別。

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