fragment中的add和replace方法的区别浅析

使用 FragmentTransaction 的时候,它提供了这样两个方法,一个 add , 一个 replace ,对这两个方法的区别一直有点疑惑。

我觉得使用 add 的话,在按返回键应该是回退到上一个 Fragment,而使用 replace 的话,那个别 replace 的就已经不存在了,所以就不会回退了。但事实不是这样子的。add 和 replace 影响的只是界面,而控制回退的,是事务。

public abstract FragmentTransaction add (int containerViewId, Fragment fragment, String tag)
Add a fragment to the activity state. This fragment may optionally also have its view (if Fragment.onCreateView returns non-null) into a Container view of the activity.

add 是把一个fragment添加到一个容器 container 里。

public abstract FragmentTransaction replace (int containerViewId, Fragment fragment, String tag)
Replace an existing fragment that was added to a container. This is essentially the same as calling remove(Fragment) for all currently added fragments that were added with the same containerViewId and then add(int, Fragment, String) with the same arguments given here.

replace 是先remove掉相同id的所有fragment,然后在add当前的这个fragment。

在大部分情况下,这两个的表现基本相同。因为,一般,咱们会使用一个FrameLayout来当容器,而每个Fragment被add 或者 replace 到这个FrameLayout的时候,都是显示在最上层的。所以你看到的界面都是一样的。但是,使用add的情况下,这个FrameLayout其实有2层,多层肯定要比一层的来得浪费,所以还是推荐使用replace。当然有时候还是需要使用add的。比如要实现轮播图的效果,每个轮播图都是一个独立的Fragment,而他的容器FrameLayout需要add多个Fragment,这样他就可以根据提供的逻辑进行轮播了。

而至于返回键的时候,这个跟事务有关,跟使用add还是replace没有任何关系。

2015.08.04 更新。

发现这篇博文被搜索得挺多的,上面是分析是在官方文档上的基础上加上一些个人的猜测,为了避免误人子弟,下面从代码实现的角度做了些分析。希望能帮到大家,也烦请大家在转载的同时注明出处,毕竟写这么一篇博文确实很不容易(binkery)。

Android Fragment 和 FragmentManager 的代码分析 这篇文章也是从代码的角度分析了 FragmentManager 的工作机制。

FragmentManager 是一个抽象类,实现类是 FragmentManagerImpl ,跟 FragmentManager 在同一个类文件里。FragmentTransaction 也是一个抽象类,具体实现是 BackStackRecord 。BackStackRecord 其实是一个封装了一个队列。咱们看 add 方法和 replace 方法。

add 方法和 replace 方法都是把一个操作 OP_XX 放入到队列里,Op 是其内部封装的一个操作的类。在 BackStackRecord 的 run 方法里,每次会从队列的头(mHead)获取一个操作 Op ,如果 Op 操作是 add ,则调用 FragmentManager 的 addFragment() 方法,如果 Op 操作是 replace ,则先调用 FragmentManager 的 removeFragment() 方法,然后再调用 addFragment() 方法。

下面是 add 方法。

public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {
 doAddOp(containerViewId, fragment, tag, OP_ADD);
 return this;
}

下面是 replace 方法。

public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) {
 if (containerViewId == 0) {
 throw new IllegalArgumentException("Must use non-zero containerViewId");
 }
 doAddOp(containerViewId, fragment, tag, OP_REPLACE);
 return this;
}

add 和 replace 方法都是调用的 doAddOp 方法。也就是把一个操作 Op 添加到队列。

private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
 fragment.mFragmentManager = mManager;
 if (tag != null) {
 if (fragment.mTag != null && !tag.equals(fragment.mTag)) {
 throw new IllegalStateException("Can't change tag of fragment "
  + fragment + ": was " + fragment.mTag
  + " now " + tag);
 }
 fragment.mTag = tag;
 }
 if (containerViewId != 0) {
 if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
 throw new IllegalStateException("Can't change container ID of fragment "
  + fragment + ": was " + fragment.mFragmentId
  + " now " + containerViewId);
 }
 fragment.mContainerId = fragment.mFragmentId = containerViewId;
 }
 Op op = new Op();
 op.cmd = opcmd;
 op.fragment = fragment;
 addOp(op);
}

run 方法才是真正执行的方法。什么时候执行先不考虑,只需要知道一系列的操作会一次执行,而不是一个操作执行一次。
run 方法有点大,就看一下 while 循环开始和结束的时候,以及 switch case 里 OP_ADD 和 OP_REPLACE 分支就可以了。

public void run() {
 if (FragmentManagerImpl.DEBUG) {
 Log.v(TAG, "Run: " + this);
 }
 if (mAddToBackStack) {
 if (mIndex < 0) {
 throw new IllegalStateException("addToBackStack() called after commit()");
 }
 }
 bumpBackStackNesting(1);
 SparseArray<Fragment> firstOutFragments = new SparseArray<Fragment>();
 SparseArray<Fragment> lastInFragments = new SparseArray<Fragment>();
 calculateFragments(firstOutFragments, lastInFragments);
 beginTransition(firstOutFragments, lastInFragments, false);
 // 获取队列的头
 Op op = mHead;
 while (op != null) {
 switch (op.cmd) {
 case OP_ADD: {
 Fragment f = op.fragment;
 f.mNextAnim = op.enterAnim;
 mManager.addFragment(f, false);//添加
 }
 break;
 case OP_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 = new ArrayList<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;
 case OP_REMOVE: {
 Fragment f = op.fragment;
 f.mNextAnim = op.exitAnim;
 mManager.removeFragment(f, mTransition, mTransitionStyle);
 }
 break;
 case OP_HIDE: {
 Fragment f = op.fragment;
 f.mNextAnim = op.exitAnim;
 mManager.hideFragment(f, mTransition, mTransitionStyle);
 }
 break;
 case OP_SHOW: {
 Fragment f = op.fragment;
 f.mNextAnim = op.enterAnim;
 mManager.showFragment(f, mTransition, mTransitionStyle);
 }
 break;
 case OP_DETACH: {
 Fragment f = op.fragment;
 f.mNextAnim = op.exitAnim;
 mManager.detachFragment(f, mTransition, mTransitionStyle);
 }
 break;
 case OP_ATTACH: {
 Fragment f = op.fragment;
 f.mNextAnim = op.enterAnim;
 mManager.attachFragment(f, mTransition, mTransitionStyle);
 }
 break;
 default: {
 throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
 }
 }
 op = op.next;//队列的下一个
 }
 mManager.moveToState(mManager.mCurState, mTransition,
 mTransitionStyle, true);
 if (mAddToBackStack) {
 mManager.addBackStackState(this);
 }
}

BackStackRecord 的构造器里参数列表里有一个 FragmentManager ,所有 BackStackRecord 其实是有一个 FragmentManager 的引用的,BackStackRecord 可以直接调用 FragmentManager 的 addFragment 方法。

下面是 FragmentManager 的 addFragment() 方法,每次 add 一个 Fragment,Fragment 对象都会被放入到 mAdded 的容器里。

public void addFragment(Fragment fragment, boolean moveToStateNow) {
 if (mAdded == null) {
 mAdded = new ArrayList<Fragment>();
 }
 if (DEBUG) Log.v(TAG, "add: " + fragment);
 makeActive(fragment);
 if (!fragment.mDetached) {
 if (mAdded.contains(fragment)) {
 throw new IllegalStateException("Fragment already added: " + fragment);
 }
 mAdded.add(fragment);
 fragment.mAdded = true;
 fragment.mRemoving = false;
 if (fragment.mHasMenu && fragment.mMenuVisible) {
 mNeedMenuInvalidate = true;
 }
 if (moveToStateNow) {
 moveToState(fragment);
 }
 }
}

有时候,咱们 add Fragment A, 然后 add Fragment B,B 把 A 都覆盖了,点击菜单的时候 A 和 B 的菜单选项都出来了,这是为什么?原因在下面。当在创建 OptionsMenu 的时候,FragmentManager 遍历了 mAdded 容器,所以 A 和 B 的菜单都被添加进来了。也就是说使用 add 的方式,虽然 B 把 A 覆盖住了,但是 A 还是存活的,而且是活动着的。

public boolean dispatchCreateOptionsMenu(Menu menu, MenuInflater inflater) {
 boolean show = false;
 ArrayList<Fragment> newMenus = null;
 if (mAdded != null) {
 for (int i=0; i<mAdded.size(); i++) {
 Fragment f = mAdded.get(i);
 if (f != null) {
 if (f.performCreateOptionsMenu(menu, inflater)) {
  show = true;
  if (newMenus == null) {
  newMenus = new ArrayList<Fragment>();
  }
  newMenus.add(f);
 }
 }
 }
 }
 if (mCreatedMenus != null) {
 for (int i=0; i<mCreatedMenus.size(); i++) {
 Fragment f = mCreatedMenus.get(i);
 if (newMenus == null || !newMenus.contains(f)) {
 f.onDestroyOptionsMenu();
 }
 }
 }
 mCreatedMenus = newMenus;
 return show;
}

小结:fragment中的add和replace方法的区别

使用add方法时,需要考虑fragment引用被清空的情况。

使用add方法add到activity里面的fragment的对象并不会被销毁。也就是它任然在activity中存在,只是应用被置为null而已。此时如果重新为fragment赋值,其hide方法和show方法都不会生效。如果这种情况下,一个activity中有多个fragment,很可能出现多个fragment层叠而不能正常的显示或者隐藏。

使用add方法使用的fragment的优点在于它占用内存资源少,通过replace方法使用fragment占用资源虽然会多一些,但是不存在add方法的bug。

所以开发的时候,尽量处理好add方法可能引起的bug。

fragment还要处理好commit和transaction.commitAllowingStateLoss()两个方法。

以上所述是小编给大家介绍的fragment中的add和replace方法的区别浅析,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • fragment中的add和replace方法的区别浅析

    使用 FragmentTransaction 的时候,它提供了这样两个方法,一个 add , 一个 replace ,对这两个方法的区别一直有点疑惑. 我觉得使用 add 的话,在按返回键应该是回退到上一个 Fragment,而使用 replace 的话,那个别 replace 的就已经不存在了,所以就不会回退了.但事实不是这样子的.add 和 replace 影响的只是界面,而控制回退的,是事务. public abstract FragmentTransaction add (int con

  • jquery中prop()方法和attr()方法的区别浅析

    jquery1.6中新加了一个方法prop(),一直没用过它,官方解释只有一句话:获取在匹配的元素集中的第一个元素的属性值. 大家都知道有的浏览器只要写disabled,checked就可以了,而有的要写成disabled = "disabled",checked="checked",比如用attr("checked")获取checkbox的checked属性时选中的时候可以取到值,值为"checked"但没选中获取值就是un

  • 浅谈Java异常的Exception e中的egetMessage()和toString()方法的区别

    Exception e中e的getMessage()和toString()方法的区别: 示例代码1: public class TestInfo { private static String str =null; public static void main(String[] args) { System.out.println("test exception"); try { if(str.equals("name")){ System.out.println

  • JavaScript中的toString()和toLocaleString()方法的区别

    偶然之间用到这两个方法 然后在数字转换成字符串的时候,并没有感觉这两个方法有什么区别,如下: var e=123 e.toString() "123" e.toLocaleString() "123" 是吧,并没有什么区别 再继续看数组转成字符串分别用这两个方法有什么区别呢,看看,代码如下 var aa=[1,2,3] aa.toLocaleString() "1,2,3" aa.toString() "1,2,3" 也并没有

  • 对Keras中predict()方法和predict_classes()方法的区别说明

    1 predict()方法 当使用predict()方法进行预测时,返回值是数值,表示样本属于每一个类别的概率,我们可以使用numpy.argmax()方法找到样本以最大概率所属的类别作为样本的预测标签. 2 predict_classes()方法 当使用predict_classes()方法进行预测时,返回的是类别的索引,即该样本所属的类别标签.以卷积神经网络中的图片分类为例说明,代码如下: 补充知识:keras中model.evaluate.model.predict和model.predi

  • C#中Abstract方法和Virtual方法的区别

    简介: c#中Abstract和Virtual比较容易混淆,都与继承有关,并且涉及override的使用.virtual可以被子类重写,而abstract必须被子类重写.virtual修饰的方法必须有实现(哪怕是仅仅添加一对大括号),而abstract修饰的方法一定不能实现.它们有一个共同点:如果用来修饰方法,前面必须添加public,要不然就会出现编译错误:虚拟方法或抽象方法是不能私有的.毕竟加上virtual或abstract就是让子类重新定义的,而private成员是不能被子类访问的.下面

  • js中string之正则表达式replace方法详解

    replace方法是javascript涉及到正则表达式中较为复杂的一个方法,严格上说应该是string对象的方法.只不过牵扯到正则的时候比较多一些.需要我们灵活的使用. 语法: stringObj.replace(regexp/substr,replacement): 第一个参数:必需.字符串中要替换的子串或正则RexExp: 第二个参数:必需,一个字符串值,规定了替换文本或生成替换文本的函数. 返回值:注意它的返回值是一个新的字符串,并没有更改原有字符串,是用 replacement 替换了

  • 在Python中操作字符串之replace()方法的使用

    replace()方法返回当前old换成new,可选择的替代限制到最大数量的字符串的副本. 语法 以下是replace()方法的语法: str.replace(old, new[, max]) 参数 old -- 这是要进行更换的旧子串. new -- 这是新的子串,将取代旧的子字符串. max -- 如果这个可选参数max值给出,仅第一计数出现被替换. 返回值 此方法返回字符串的拷贝与旧子串出现的所有被新的所取代.如果可选参数最大值给定,只有第一个计数发生替换. 例子 下面的示例演示了repl

  • 解析thinkphp中的M()与D()方法的区别

    D()和M()方法的区别:D和M的区别主要在于M方法不需要创建模型类文件,M方法不会读取模型类,所以默认情况下自动验证是无效的,但是可以通过动态赋值的方式实现而D方法必须有创建模型类.我们可以用下面两种方法去创建一个数据表的映射对象第一种:$Test = D('Test')第二种:$Test = new Model('Test')虽然这两种都可以对数据进行select,insert,delete,udpate操作,在数据验证上有很大的不同,用第一种方式实例一个模型就会有数据检查功能,如果 tit

  • 关于python中不同函数读取图片格式的区别浅析

    目录 前言:关于uint8和float型数据 一.skimage.io. 二.imageio 补充:python处理图像转化为uint8格式 总结 前言:关于uint8和float型数据 为了节省存储空间,图像一般存储在一种特殊的数据类型——uint8(8位无符号整数)中,以此方式存储的图像称作8位图像. imread把灰度图像存入一个8位矩阵,当为RGB图像时,就存入8位RGB矩阵中. 因此,通过matlab以及各种python包(skimage,imageio)读入图像的数据类型都是uint

随机推荐