全网整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:400-708-3566

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


# fragment  # add  # replace  # replace方法  # fragment的add方法  # 都是  # 是一个  # 多个  # 这两个  # 情况下  # 小编  # 博文  # 这是  # 是在  # 我觉得  # 抽象类  # 是有  # 在此  # 基础上  # 什么时候  # 他就  # 没有任何  # 遍历  # 中有  # 只需 


相关文章: 怎么制作网站设计模板图片,有电商商品详情页面的免费模板素材网站推荐吗?  孙琪峥织梦建站教程如何优化数据库安全?  大连网站设计制作招聘信息,大连投诉网站有哪些?  家庭建站与云服务器建站,如何选择更优?  网站设计制作企业有哪些,抖音官网主页怎么设置?  建站之星微信建站一键生成小程序+多端营销系统  ,怎么在广州志愿者网站注册?  建站一年半SEO优化实战指南:核心词挖掘与长尾流量提升策略  网站制作培训多少钱一个月,网站优化seo培训课程有哪些?  临沂网站制作公司有哪些,临沂第四中学官网?  如何在腾讯云服务器上快速搭建个人网站?  建站10G流量真的够用吗?如何应对访问高峰?  手机钓鱼网站怎么制作视频,怎样拦截钓鱼网站。怎么办?  惠州网站建设制作推广,惠州市华视达文化传媒有限公司怎么样?  C++中的Pimpl idiom是什么,有什么好处?(隐藏实现)  制作网站的软件下载免费,今日头条开宝箱老是需要下载怎么回事?  建站org新手必看:2024最新搭建流程与模板选择技巧  如何用AWS免费套餐快速搭建高效网站?  设计网站制作公司有哪些,制作网页教程?  如何通过免费商城建站系统源码自定义网站主题与功能?  教学网站制作软件,学习*后期制作的网站有哪些?  如何确保西部建站助手FTP传输的安全性?  网站制作和推广的区别,想自己建立一个网站做推广,有什么快捷方法马上做好一个网站?  如何快速打造个性化非模板自助建站?  如何用PHP快速搭建CMS系统?  如何快速搭建二级域名独立网站?  微信小程序 五星评分(包括半颗星评分)实例代码  简历在线制作网站免费,免费下载个人简历的网站是哪些?  电影网站制作价格表,那些提供免费电影的网站,他们是怎么盈利的?  网站制作哪家好,cc、.co、.cm哪个域名更适合做网站?  如何通过cPanel快速搭建网站?  建站之星如何一键生成手机站?  零服务器AI建站解决方案:快速部署与云端平台低成本实践  深圳网站制作的公司有哪些,dido官方网站?  c# 在ASP.NET Core中管理和取消后台任务  SQL查询语句优化的实用方法总结  弹幕视频网站制作教程下载,弹幕视频网站是什么意思?  Swift开发中switch语句值绑定模式  如何用wdcp快速搭建高效网站?  巅云智能建站系统:可视化拖拽+多端适配+免费模板一键生成  济南专业网站制作公司,济南信息工程学校怎么样?  制作国外网站的软件,国外有哪些比较优质的网站推荐?  建站之星如何修改网站生成路径?  建站主机核心功能解析:服务器选择与网站搭建流程指南  如何用手机制作网站和网页,手机移动端的网站能制作成中英双语的吗?  ,网站推广常用方法?  专业型网站制作公司有哪些,我设计专业的,谁给推荐几个设计师兼职类的网站?  网站制作专业公司有哪些,如何制作一个企业网站,建设网站的基本步骤有哪些?  免费网站制作appp,免费制作app哪个平台好?  怎么将XML数据可视化 D3.js加载XML 

您的项目需求

*请认真填写需求信息,我们会在24小时内与您取得联系。