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()可以去修改源码,如果你不嫌麻烦的话。。。
FragmentTransaction.replace() 你不知道的坑的更多相关文章
- 怎么通过activity里面的一个按钮跳转到另一个fragment(android FragmentTransaction.replace的用法介绍)
即:android FragmentTransaction.replace的用法介绍 Fragment的生命周期和它的宿主Activity密切相关,几乎和宿主Activity的生命周期一致,他们之间最 ...
- 解决fragmentTransaction.replace不能全屏
今天遇到个问题,使用fragmentTransaction.replace替换后的内容不能全屏.. FragmentManager fragmentManager = getSupportFragme ...
- replace的坑
问题:html中代码段包含了$,在使用replace替换时,$直接被替换了解决:先把文本中的$全部替换成自己定义的标签,最后在还原回去原因:在介绍replace的文档中,$&代表插入匹配的子串 ...
- java调用第三方命令,process.waitfor()挂起(你不知道的坑)
我们常在java中运行第三方程序,如sh.python,java提供一个Runtime.exec()方法,生成一个Process对象.今天在使用这个方法的时候,发现接口半天没有返回数据.查了一下,原来 ...
- MySQL 主从同步架构中你不知道的“坑”(2)
指定同步库情况 1.binlog_format= ROW模式 mysql> use testdb; Database changed mysql> show tables; +----- ...
- MySQL 主从同步架构中你不知道的“坑”
以下操作征对指定不同步库 binlog-format=ROW模式 1 查看主从的binlog模式 mysql> show slave status\G ********************* ...
- LVS+Keepalived深度理解,阐述你不知道的坑点
1. LVS简介 1. 什么是LVS? LVS是Linux Virtual Server的简写,意即Linux虚拟服务器,是一个虚拟的服务器集群系统.本项目在1998年5月由章文嵩博士成立,是中国国内 ...
- 错误:The method replace(int, Fragment) in the type FragmentTransaction is not applicable for the arguments (int, MyFragment)
Fragment newfragment =new MyFragment();fragmentTransaction.replace(R.layout.activity_main,newfragmen ...
- FragmentTransaction add 和 replace 区别 转
使用 FragmentTransaction 的时候,它提供了这样两个方法,一个 add , 一个 replace . add 和 replace 影响的只是界面,而控制回退的,是事务. public ...
随机推荐
- IOS开发--微信支付
前言:下面介绍微信支付的开发流程的细节,图文并茂,你可以按照我的随笔流程过一遍代码.包你也学会了微信支付.而且支付也是面试常问的内容. 正文: 1.首先在开始使用微信支付之前,有一些东西是开发者必须要 ...
- Android 短信验证码平台
1.容联云通讯 (目前只支持国内.收费) http://www.yuntongxun.com/ 2.bmob (目前只支持国内.收费) http://www.bmob.cn/ 3.阿里大鱼(目前只支持 ...
- Android 在不同Actitity之间数据传递
本文实现一个简易的人品计算器来实践在不同Actitity之间数据传递 intent的数据传递 从A界面打开B界面 把A界面的数据传递给B界面 1. intent.setData(uri) -- int ...
- Java中的经典算法之选择排序(SelectionSort)
Java中的经典算法之选择排序(SelectionSort) 神话丿小王子的博客主页 a) 原理:每一趟从待排序的记录中选出最小的元素,顺序放在已排好序的序列最后,直到全部记录排序完毕.也就是:每一趟 ...
- 【代码笔记】iOS-调用系统震动和声音
代码: RootViewController.m #import "RootViewController.h" #import <AudioToolbox/AudioTool ...
- 【代码笔记】iOS-3个section,每个都有header.
一,效果图: 二,工程目录. 三,代码 RootViewController.h #import <UIKit/UIKit.h> @interface RootViewController ...
- TCP/IP 协议难点之一—— IP分片
1 IP协议简单介绍 就个人而言,网络中,抛开网络安全加密这些,就只单单讨论协议本身,比较难的有三个地方: IP分片与重组 TCP滑动窗口与TCP状态的改变 TCP定时器 其实协议本身根据<TC ...
- .net C# 图片转Base64 Base64转图片
//图片 转为 base64编码的文本 private void button1_Click(object sender, EventArgs e) { OpenFileDialog dlg = ne ...
- ror 在windows下开发的坑
虽然知道ror在windows下的坑很多很多,但是目前没有先将就入门学习,也不折腾了.后面等待新机器来了,用linux来搭平台,先记录一下遇到的坑. 1.views/layouts/applicati ...
- 写在复习MVC后
MVC的一些 今天把MVC复习了下,包括官方文档以及各种中文博客. 官方文档里面最能说明的问题的图片,相对于传统的MVC,苹果分离了View和Model之间的通信,实现了更好的复用性.我觉得MVC更 ...