热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

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

使用FragmentTransaction的时候,它提供了这样两个方法,一个add,一个replace,对这两个方法的区别一直有点疑惑。下面小编通过本文给大家简单介绍下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 firstOutFragments = new SparseArray();
 SparseArray lastInFragments = new SparseArray();
 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 

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();
 }
 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 newMenus = null;
 if (mAdded != null) {
 for (int i=0; i();
  }
  newMenus.add(f);
 }
 }
 }
 }
 if (mCreatedMenus != null) {
 for (int i=0; i

小结: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方法的区别浅析,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!


推荐阅读
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 本文讨论了Alink回归预测的不完善问题,指出目前主要针对Python做案例,对其他语言支持不足。同时介绍了pom.xml文件的基本结构和使用方法,以及Maven的相关知识。最后,对Alink回归预测的未来发展提出了期待。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文讲述了如何通过代码在Android中更改Recycler视图项的背景颜色。通过在onBindViewHolder方法中设置条件判断,可以实现根据条件改变背景颜色的效果。同时,还介绍了如何修改底部边框颜色以及提供了RecyclerView Fragment layout.xml和项目布局文件的示例代码。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • 【Windows】实现微信双开或多开的方法及步骤详解
    本文介绍了在Windows系统下实现微信双开或多开的方法,通过安装微信电脑版、复制微信程序启动路径、修改文本文件为bat文件等步骤,实现同时登录两个或多个微信的效果。相比于使用虚拟机的方法,本方法更简单易行,适用于任何电脑,并且不会消耗过多系统资源。详细步骤和原理解释请参考本文内容。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
author-avatar
大爱保罗2502904621
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有