FragmentTransaction.replace() 你不知道的坑

  1. 云栖社区>
  2. 博客>
  3. 正文

FragmentTransaction.replace() 你不知道的坑

技术小胖子 2018-03-25 23:56:21 浏览2226
展开阅读全文

一、起源:

先看效果,在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 =newFragment2();

                 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()可以去修改源码,如果你不嫌麻烦的话。。。



    本文转自 一点点征服   博客园博客,原文链接:http://www.cnblogs.com/ldq2016/p/6245968.html,如需转载请自行联系原作者


网友评论

登录后评论
0/500
评论
技术小胖子
+ 关注