Fragment add与replace的区别

346 阅读5分钟

Fragment add与replace的区别,以及对Fragment的生命周期影响

题目:Fragment add与replace的区别,对Fragment的生命周期影响

这道题想考察什么?

  1. 是否熟悉add和replace的应用场景

  2. 是否熟悉Fragment的事务操作后的处理流程

考察的知识点

  1. add和replace的应用场景

  2. Fragment事务操作后的处理流程

考生应该如何回答

  1. 首先我们先从add和replace应用场景出发

    当我们对Activity通过事务添加一个Fragment时,有两种方式都可以做到,那就是add和replace。

    • 当前Activity同一个容器只添加一个Fragment时add和replace效果是一样的。

    • 当前Activity添加了一个FragmentA后,在后续某个操作中要将这个FragmentA替换为另外一个FragmentB时,有两种情况:

      1. 如果FragmentA时通过add添加的,在将FragmentA替换为FragmentB时,可以通过hide FragmentA,add FragmentB show FragmentB

      2. 如果FragmentA通过replace操作添加的,在将FragmentA替换为FragmentB时,使用replace替换

    • 通过 add 添加的 Fragments,可以由开发者自由控制当前应该显示哪个Fragment,以及隐藏哪个Fragment,其实在源码中就是对应Fragment中View的显示和隐藏。

    • 通过replace 添加 的 Fragment,当前已存在的Fragment被替换时走生命周期销毁流程,传递给replace方法的Fragment走生命周期创建流程。

    • 根据以上结论,我们可以得到一个实例开发中添加Fragment时很重要的一个结论

      • 当Fragment不可见时,如果你要保留Fragment中的数据以及View的显示状态,那么可以使用add操作,后续中针对不同的状态隐藏和显示不同的Fragment。

        • 优点:快,知识Fragment中View的显示和隐藏

        • 缺点:内存中保留的数据太多,容易导致造成OOM的风险。

      • 当Fragment不可见时,你不需要保留Fragment中的数据以及View的显示状态,那么可以使用replace。

        • 优点:节省内存,不需要的数据能立即释放掉

        • 缺点:频繁创建Fragment,也就是频繁走Fragment生命周期创建和销毁流程,造成性能开销。

  2. 然后分别对进行一个add和replace事物操作时,源码中是如何处理的。

    要对add和replace事务操作源码中是如何处理的,那么先看下对Fragment事务操作提交流程。

    简要流程图

    点击查看大图

    从流程图中发现当执行第10步骤,也就是当调用BackStackRecord#expandOps方法时,对replace操作会进行修正

    <pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n58" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">//BackStackRecord#expandOps
    Fragment expandOps(ArrayList<Fragment> added, Fragment oldPrimaryNav) {
     for (int opNum = 0; opNum < mOps.size(); opNum++) {
     final Op op = mOps.get(opNum);
     switch (op.cmd) {
     case OP_ADD:
     case OP_ATTACH:
     added.add(op.fragment);
     break;
     case OP_REMOVE:
     case OP_DETACH: {
     added.remove(op.fragment);
     }
     case OP_REPLACE: {
     final Fragment f = op.fragment;
     final int containerId = f.mContainerId;
     boolean alreadyAdded = false;
     for (int i = added.size() - 1; i >= 0; i--) {
     //如果当前已有Fragment添加到Activity中,取出已添加的Fragment
     final Fragment old = added.get(i);
     if (old.mContainerId == containerId) {
     if (old == f) {
     //如果replace的是同一个Fragment
     alreadyAdded = true;
     } else {
     //不是同一Fragment,删除旧的Fragment
     final Op removeOp = new Op(OP_REMOVE, old);
     removeOp.enterAnim = op.enterAnim;
     removeOp.popEnterAnim = op.popEnterAnim;
     removeOp.exitAnim = op.exitAnim;
     removeOp.popExitAnim = op.popExitAnim;
     mOps.add(opNum, removeOp);
     added.remove(old);
     opNum++;
     }
     }
     }
     if (alreadyAdded) {
     //从操作序列中移除当前请求的操作,表示当前操作无效
     mOps.remove(opNum);
     opNum--;
     } else {
     //当前Activity中同一个id没有添加Fragment或者同一个id存在Fragment,但replace传递fragment和已有的不是同一个实例,将操作类型replace修改为add
     op.cmd = OP_ADD;
     added.add(f);
     }
     }
     break;
     }
     }
     return oldPrimaryNav;
     }</pre>

由上面代码分析可知

  1. 如果当前Activity同一个id还没有添加Fragment,replace操作和add操作一样。

    即执行两者操作生命周期变化:onAttach->onCreate->onCreateView->onActivityCreated->onStart-onResume,

    Fragment所依附的Activity销毁时,执行onPause->onStop->onDestoryView->onDestory->onDetach

2.  如果当前Activity同一个id存在Fragment,replace传递的Fragment实例和已存在的Fragment实例一样,replace无效果,这点从上面源码中可以看出
3.  如果当前Activity同一个id存在Fragment,replace传递的Fragment实例和已存在的Fragment实例不一样,replace操作会转换为 remove和add操作,即删除旧的Fragment,添加新的Fragment,这点从上面代码中也可以看出来。

    旧的Fragment执行 onPause->onStop->onDestoryView->onDestory->onDetach

    新的Fragment执行 onAttach->onCreate->onCreateView->onActivityCreated->onStart-onResume

总的来说BackStackRecord#expandOps就是修正replace的操作,将replace 变为 remove和add操作。

无论是remove,add 我们发现操作的都是mOps集合。mOps中存放的是事务操作的序列。根据流程图在接下来第11步骤也就是BackStackRecord#executeOps会执行mOps存放的事务操作。

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="java" cid="n73" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">void executeOps() {
 final int numOps = mOps.size();
 for (int opNum = 0; opNum < numOps; opNum++) {
 final Op op = mOps.get(opNum);
 final Fragment f = op.fragment;
 switch (op.cmd) {
 case OP_ADD:
 f.setNextAnim(op.enterAnim);
 mManager.addFragment(f, false);
 break;
 case OP_REMOVE:
 f.setNextAnim(op.exitAnim);
 mManager.removeFragment(f);
 break;
 case OP_HIDE:
 f.setNextAnim(op.exitAnim);
 mManager.hideFragment(f);
 break;
 case OP_SHOW:
 f.setNextAnim(op.enterAnim);
 mManager.showFragment(f);
 break;
 case OP_DETACH:
 f.setNextAnim(op.exitAnim);
 mManager.detachFragment(f);
 break;
 case OP_ATTACH:
 f.setNextAnim(op.enterAnim);
 mManager.attachFragment(f);
 break;
 case OP_SET_PRIMARY_NAV:
 mManager.setPrimaryNavigationFragment(f);
 break;
 case OP_UNSET_PRIMARY_NAV:
 mManager.setPrimaryNavigationFragment(null);
 break;
 default:
 throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
 }
 ...
 }
 //执行Fragment生命周期
 mManager.moveToState(mManager.mCurState, true);
}</pre>
  1. 最后简单总计下:

面试官在问add和replace区别,我们可以从实际使用中真实感受出发 即第一点回答,描述完区别后;再来从源码角度描述replace本质是什么,replace本质就是将replace操作变为remove和add操作,旧的 Fragment走生命周期销毁流程,新传递的Fragment走生命周期创建流程。 如果要从源码角度回答细致一点,那么可以回答: 1. 如果当前Activity同一个id还没有添加Fragment,replace操作和add操作一样。 即执行两者操作生命周期变化:onAttach->onCreate->onCreateView->onActivityCreated->onStart-onResume, Fragment所依附的Activity销毁时,执行onPause->onStop->onDestoryView->onDestory->onDetach 2. 如果当前Activity同一个id存在Fragment,replace传递的Fragment实例和已存在的Fragment实例一样,replace无效果。 3. 如果当前Activity同一个id存在Fragment,replace传递的Fragment实例和已存在的Fragment实例不一样,replace操作会转换为 remove和add操作,即删除旧的Fragment,添加新的Fragment。 旧的Fragment执行 onPause->onStop->onDestoryView->onDestory->onDetach 新的Fragment执行 onAttach->onCreate->onCreateView->onActivityCreated->onStart-onResume

相关视频

Android中高级大厂面试activity/fragment/service/brroadcast/binder