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

  • 相关阅读:
    vim 打开了没有权限写入的文件,怎么才能不必重新编辑
    Linux 关闭终端响铃的特性
    Python 的 print 函数
    C 语言标准库中的qsort函数使用
    java知识学习14-面向对象
    java知识学习13-方法
    java知识学习12-数组
    mac格式化重装系统
    java知识学习11- IDEA
    java知识学习10-跳转控制语句(break/continue)、循环嵌套、Random
  • 原文地址:https://www.cnblogs.com/ldq2016/p/6245968.html
Copyright © 2020-2023  润新知