题目:Fragment add与replace的区别,对Fragment的生命周期影响
这道题想考察什么?
- 是否熟悉add和replace的应用场景
- 是否熟悉Fragment的事务操作后的处理流程
考察的知识点
- add和replace的应用场景
- Fragment事务操作后的处理流程
考生应该如何回答
-
首先我们先从add和replace应用场景出发
当我们对Activity通过事务添加一个Fragment时,有两种方式都可以做到,那就是add和replace。
- 当前Activity同一个容器只添加一个Fragment时add和replace效果是一样的。
- 当前Activity添加了一个FragmentA后,在后续某个操作中要将这个FragmentA替换为另外一个FragmentB时,有两种情况:
- 如果
FragmentA
时通过add添加的,在将FragmentA
替换为FragmentB
时,可以通过hideFragmentA
,addFragmentB
showFragmentB
- 如果
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生命周期创建和销毁流程,造成性能开销。
- 当Fragment不可见时,如果你要保留Fragment中的数据以及View的显示状态,那么可以使用add操作,后续中针对不同的状态隐藏和显示不同的Fragment。
-
然后分别对进行一个add和replace事物操作时,源码中是如何处理的。
要对add和replace事务操作源码中是如何处理的,那么先看下对Fragment事务操作提交流程。
简要流程图
从流程图中发现当执行第10步骤,也就是当调用BackStackRecord#expandOps方法时,对replace操作会进行修正
//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; }
由上面代码分析可知
-
如果当前Activity同一个id还没有添加Fragment,replace操作和add操作一样。
即执行两者操作生命周期变化:onAttach->onCreate->onCreateView->onActivityCreated->onStart-onResume,
Fragment所依附的Activity销毁时,执行onPause->onStop->onDestoryView->onDestory->onDetach
-
如果当前Activity同一个id存在Fragment,replace传递的Fragment实例和已存在的Fragment实例一样,replace无效果,这点从上面源码中可以看出
-
如果当前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存放的事务操作。
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); }
-
最后简单总计下:
面试官在问add和replace区别,我们可以从实际使用中真实感受出发 即第一点回答,描述完区别后;再来从源码角度描述replace本质是什么,replace本质就是将replace操作变为remove和add操作,旧的 Fragment走生命周期销毁流程,新传递的Fragment走生命周期创建流程。
如果要从源码角度回答细致一点,那么可以回答:
-
如果当前Activity同一个id还没有添加Fragment,replace操作和add操作一样。
即执行两者操作生命周期变化:onAttach->onCreate->onCreateView->onActivityCreated->onStart-onResume,
Fragment所依附的Activity销毁时,执行onPause->onStop->onDestoryView->onDestory->onDetach
-
如果当前Activity同一个id存在Fragment,replace传递的Fragment实例和已存在的Fragment实例一样,replace无效果。
-
如果当前Activity同一个id存在Fragment,replace传递的Fragment实例和已存在的Fragment实例不一样,replace操作会转换为 remove和add操作,即删除旧的Fragment,添加新的Fragment。
旧的Fragment执行 onPause->onStop->onDestoryView->onDestory->onDetach
新的Fragment执行 onAttach->onCreate->onCreateView->onActivityCreated->onStart-onResume
-
本文在开源项目:github.com/Android-Alv… 中已收录,里面包含不同方向的自学编程路线、面试题集合/面经、及系列技术文章等,资源持续更新中...
-