1. 程式人生 > >FragmentTransaction.replace() 你不知道的坑

FragmentTransaction.replace() 你不知道的坑

一、起源:

先看效果,在linearLayout中添加了4個Fragment,然後點選替換一次確替換了兩個Fragment,引發了我的研究興趣;

第一次啟動                                  點選一次                         點選兩次                              點選三次

程式碼很簡單  activity  onCreate 方法中添加了4個Fragment

           FragmentTransaction transaction =manager.beginTransaction();

           transaction.add(R.id.content,fragment1,"a");

           transaction.add(R.id.content,fragment1_2,"b");

           transaction.add(R.id.content,fragment1_3,"c");

           transaction.add(R.id.content,fragment1_4,"d");

           transaction.commit();

replace 按鈕監聽事件中添加了如下程式碼

Fragment2 fragment2_1 =new

Fragment2();

                 FragmentTransaction transaction=manager.beginTransaction();

                 transaction.replace(R.id.content,fragment2_1,"kk");

                 transaction.commit();

二、探究transaction.replace到底做了什麼

探究原始碼得知FragmentTransaction 物件是在FragmentManagerImpl 類中的beginTransaction()方法中產生的;

@Override

    publicFragmentTransaction beginTransaction() {

       returnnewBackStackRecord(this);

    }

這才發現BackStackRecord產生的物件才是我們真正使用的FragmentTransaction,那BackStackRecord.replace()  方法究竟做了啥,讓我們一探究竟;

    publicFragmentTransactionreplace(intcontainerViewId, Fragment fragment, String tag) {

        doAddOp(containerViewId, fragment, tag,OP_REPLACE);

        return this;

    }

public FragmentTransactionadd(intcontainerViewId, Fragment fragment, String tag) {

       doAddOp(containerViewId, fragment, tag,OP_ADD);

        return this;

   }

可以看到add和 replace  方法都沒有自己去處理而是交給doAddOp處理,doAddOp()簡化程式碼如下

privatevoiddoAddOp(intcontainerViewId, Fragment fragment, String tag,int opcmd){

        fragment.mFragmentManager = mManager;

if (tag!=null) {

               fragment.mTag = tag;

        }

        if(containerViewId != 0) {

           fragment.mContainerId = fragment.mFragmentId =containerViewId;

        }

      Op op =new Op();

        op.cmd =opcmd;

        op.fragment =fragment;

        addOp(op);

    }

我們發現,add  和replace  方法都是進行了fragment物件的tag、mFragmentId、mContainerId的賦值,mContainerId是容器id,也就是說每一個fragment 都有保留它被新增的容器的id,也就是我們replace傳入的R.id,content;

再看看OP

static final class Op {

Op next;

Op prev;

int cmd;

Fragment fragment;

int enterAnim;

int exitAnim;

int popEnterAnim;

int popExitAnim;

ArrayList<Fragment> removed;

}
Op其實就是儲存我們處理動作的一個物件,經過一系列追蹤發現,最終在BackStackRecord.run()中處理了這個物件;
具體的追蹤過程可以參考:https://zhuanlan.zhihu.com/p/20660984

 處理原始碼如下:

switch (op.cmd) {

                caseOP_ADD: {

                    Fragment f = op.fragment;

                    f.mNextAnim = op.enterAnim;

                    mManager.addFragment(f,false);

                } break;

                caseOP_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 =newArrayList<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;

好了終於找到replace的真正處理之處,我們精練出關鍵程式碼再看看:

switch (op.cmd) {

                caseOP_ADD: {

                    Fragment f = op.fragment;

                    mManager.addFragment(f,false);

                } break;

                caseOP_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 (f == null ||old.mContainerId == f.mContainerId) {

                                if (old== f) {

                                    op.fragment = f =null;

                                } else {

                                    mManager.removeFragment(old,mTransition,mTransitionStyle);

                                }

                            }

                        }

                    }

                    if (f !=null) {

                             mManager.addFragment(f,false);

                    }

                } break;

可以看到

1、add方法就是呼叫了fragmentmanager的新增方法;

2、replace 則是先刪除fragmentmanager中所有已新增的fragment中,容器id與當前要新增的fragment的容器id相同的fragment;然後再添加當前fragment; 

3、由於新增的時候都是在一個LinearLayout 中,那麼所有的 fragment的容器Id都是一樣的;

得出結論: replace 會刪除LinearLayout中所有fragment  ,然後再新增傳入fragment物件;

好,問題來了,最開始的圖片點選第一次刪除的是fragment1和fragment1_3  ,第二次只刪除了fragment1_3,並沒有刪除全部,這又是為什麼;

帶著疑問的態度進行了一次除錯,在除錯中終於找到了原因,問題就在這段程式碼:

for (int i=0; i<mManager.mAdded.size(); i++) {

    Fragment old = mManager.mAdded.get(i);

    if (f ==null ||old.mContainerId == f.mContainerId) {

        mManager.removeFragment(old,mTransition, mTransitionStyle);

}

}

mManager.mAdded  是一個ArrayList<Fragment>  列表,在遍歷的時候呼叫了mManager.removeFragment方法,而該方法呼叫了ArrayList的remove方法;

  public void removeFragment(Fragmentfragment, int transition, inttransitionStyle) {

                mAdded.remove(fragment);

  }

也就是說在用for迴圈遍歷ArrayList列表的時候使用了remove;這是開始懷戀我們的Java老師的了,list遍歷列表要刪除元素我們要用iterator.remove();

For迴圈遍歷過程刪除會造成ArrayList.size()不斷變小,所以造成刪除不完全的問題;你是否也被坑過。。。

筆記建議  Android此處可以將 mManager.mAdded複製一份再遍歷,就不會有這個問題了(親測有效);

ArrayList<Fragment> list=new ArrayList<Fragment>(mManager.mAdded )  ;    
                for (int i=0; i<list.size();i++) {
                          Fragment old = list.get(i);

    if (f ==null ||old.mContainerId == f.mContainerId) {

        mManager.removeFragment(old,mTransition, mTransitionStyle);

  }

}


三、總結

用於我們常常使用FrameLayout 做容器fragment都掩蓋了下面其他Fragment,大部分情況下看不到此類問題,看不到不表示不存在,筆者建議,遇到此類還是手動去呼叫remove+add方法,一定要使用replace()可以去修改原始碼,如果你不嫌麻煩的話。。。