文档章节

FragmentTransaction.replace() 你不知道的坑

hejunbinlan
 hejunbinlan
发布于 2016/10/28 13:48
字数 1094
阅读 46
收藏 0

一、起源:

先看效果,在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://blog.csdn.net/baidu_28534147/article/details/51713172

hejunbinlan
粉丝 42
博文 596
码字总数 21569
作品 0
浦东
高级程序员
私信 提问
KJFrameForAndroid 2.24 发布,Android 开发框架

<>KJFrameForAndroid 的设计思想是通过封装Android原生SDK中复杂的复杂操作而达到简化Android应用级开发,最终实现快速而又安全高效的开发APP。目标是用最少的代码,完成最多的操作,用最高的...

kymjs张涛
2015/08/05
1K
4
微信小程序踩坑录(canvas、post请求不生效、页面刷新)

作者:汪娇娇 日期:2016.11.24 现在也不知道距离微信公测多少日子了,反正感觉我是埋在微信小程序这个坑里很久了,公司的项目终于快接近尾声,现在就腾点时间记录下我的收获,希望能给大家一...

娇娇jojojo
2016/11/24
503
6
外包公司很坑吗?

没有如果天堂就不知道啥是地狱,之前一直在外包公司工作,不知道非外包公司是什么样子,另外问下外包公司很坑吗?真的象传说中的那样吗?

疯狂的兔子
2014/02/27
1K
7
上班太闲,能干点什么呢?

RT 现在工作,太闲了,国企相当坑,全部在玩.......... 现在空闲的时间太多了!想做点什么,老是玩,有点慌啊。。。。 想傻逼一把去读个研,但是不知道能不能考上而且考上了貌似也没啥用(听...

黑狗
2012/10/19
2.6K
17
喷下干货。。。

看了一个所谓很有干货的文章。想起内涵和外延的定理。内涵越多的,外延越少,反之亦然。也就是所谓,内涵越多的货,越干。干到最后,就是个石头。喜欢干货的,无非身上带的石头少,遇到问题,...

中山野鬼
2013/10/15
828
10

没有更多内容

加载失败,请刷新页面

加载更多

ngrok 外网映射工具

ngrok介绍 许多刚学java web的同学(包括我自己)肯定都非常好奇,如何在外网上访问自己做的项目,跟我们本地访问tomcat有什么区别? 今天就向大家介绍一个非常强大的外网映射工具:ngrok.ngrok可以...

edison_kwok
50分钟前
2
0
Spark Streaming的优化之路——从Receiver到Direct模式

          作者:个推数据研发工程师 学长 1 业务背景 随着大数据的快速发展,业务场景越来越复杂,离线式的批处理框架MapReduce已经不能满足业务,大量的场景需要实时的数据处理结果来...

个推
今天
4
0
壮丽70年·奋斗新时代|蒸妙集团熏蒸中会阴熏蒸的神奇好处

聚结相合之处为会。会阴居两阴间,为督、任、冲三脉的起点,三脉背出两阴之间,会聚阴部,因名会阴。会阴,经穴名。出《针灸甲乙经》。会阴别名屏翳、下极、金门。属任脉。在会阴部,男性当阴...

公益传承
今天
2
0
pentaho-kettle-8.2.0.0-R源码开发环境搭建

1.从Kettle官网下载源码,本文使用的是pentaho-kettle-8.2.0.0-R 下载地址:https://codeload.github.com/pentaho/pentaho-kettle/zip/8.2.0.0-R 2.打开eclipse,选择一个新的工作空间,然后设...

gq_2010
今天
1
0
lua web快速开发指南(7) - 高效的接口调用 - httpc库

httpc库基于cf框架都内部实现的socket编写的http client库. httpc库内置SSL支持, 在不使用代理的情况下就可以请求第三方接口. httpc支持header、args、body、timeout请求设置, 完美支持各种h...

水果糖的小铺子
今天
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部