序
实习中发现,对于FragmenManager管理Fragment有时候使用add的方式,有时候使用replace的方式。
遂问大佬这有什么讲究,大佬说replace多用于一次性显示,add和hide用于多次切换
然而,对于这个结论我依然未能满足,好奇心驱使之下决定探究下这里面的道理
正文
Note:研究基于AndroidX
通常通过FragmentManager获取FragmentTransaction对Fragment进行管理时,可通过add和replace的方式添加Fragment,为探究两者的区别,决定从源码进行分析
不同版本的实现会有一定出入,但是通过FragmentTransaction定义的行为却是相同的
FragmentTransaction是一个抽象类,定义了包括add、show、hide、replace等的行为准则
public FragmentTransaction replace(@IdRes int containerViewId, @NonNull Fragment fragment,
@Nullable String tag) {
/**
* Replace an existing fragment that was added to a container. This is
* essentially the same as calling {@link #remove(Fragment)} for all
* currently added fragments that were added with the same containerViewId
* and then {@link #add(int, Fragment, String)} with the same arguments
* given here.
...
*/
@NonNull
public FragmentTransaction replace(@IdRes int containerViewId, @NonNull Fragment fragment,
@Nullable String tag) {
...
}
/**
* Add a fragment to the activity state. This fragment may optionally
* also have its view (if {@link Fragment#onCreateView Fragment.onCreateView}
* returns non-null) into a container view of the activity.
...
*/
@NonNull
public FragmentTransaction add(@IdRes int containerViewId, @NonNull Fragment fragment,
@Nullable String tag) {
...
}
}
FragmentTransaction定义了add就是直接的添加,而replace是先remove再add
Fragment类之中包含众多属性,其中与add或replace相关的属性记录如下
public class Fragment implements ComponentCallbacks, OnCreateContextMenuListener, LifecycleOwner,
ViewModelStoreOwner, SavedStateRegistryOwner {
...
// When a fragment is being dynamically added to the view hierarchy, this
// is the identifier of the parent container it is being added to.
int mContainerId;
// Set to true when the app has requested that this fragment be hidden
// from the user.
boolean mHidden;
...
}
Fragment中包含的mContainerId是作为其父容器的标识
接下来记录FragmentManager的实现链路
AppCompatActivity
- FragmentActivity
- FragmentController
- FragmentHostCallback
- FragmentManagerImpl
- BackStackRecord
基本上关于FragmentManager中的操作都在FragmentManagerImpl类中提供实现方法,但是关于其管理,还是封装在了BackStackRecord中
FragmentManagerImpl
final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory2 {
...
final ArrayList<Fragment> mAdded = new ArrayList<>();
ArrayList<BackStackRecord> mBackStack;
...
}
添加的Fragment会添加到mAdded之中
BackStackRecord
Note:展示关键性代码,关于add、remove、hide、show和replace的,这个判断分支并不都在一个方法中
switch (op.mCmd) {
case OP_ADD:
f.setNextAnim(op.mEnterAnim);
mManager.addFragment(f, false);
break;
case OP_REMOVE:
f.setNextAnim(op.mExitAnim);
mManager.removeFragment(f);
break;
case OP_HIDE:
f.setNextAnim(op.mExitAnim);
mManager.hideFragment(f);
break;
case OP_SHOW:
f.setNextAnim(op.mEnterAnim);
mManager.showFragment(f);
break;
...
case OP_REPLACE: {
final Fragment f = op.mFragment;
final int containerId = f.mContainerId;
boolean alreadyAdded = false;
for (int i = added.size() - 1; i >= 0; i--) {
final Fragment old = added.get(i);
if (old.mContainerId == containerId) {
if (old == f) {
alreadyAdded = true;
} else {
// This is duplicated from above since we only make
// a single pass for expanding ops. Unset any outgoing primary nav.
if (old == oldPrimaryNav) {
mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, old));
opNum++;
oldPrimaryNav = null;
}
final Op removeOp = new Op(OP_REMOVE, old);
removeOp.mEnterAnim = op.mEnterAnim;
removeOp.mPopEnterAnim = op.mPopEnterAnim;
removeOp.mExitAnim = op.mExitAnim;
removeOp.mPopExitAnim = op.mPopExitAnim;
mOps.add(opNum, removeOp);
added.remove(old);
opNum++;
}
}
}
if (alreadyAdded) {
mOps.remove(opNum);
opNum--;
} else {
op.mCmd = OP_ADD;
added.add(f);
}
}
break;
...
add、remove的实现封装在FragmentManagerImpl类的addFragment和removeFragment方法中,其核心实现就是往mAdded中add或remove当前Fragment
hdie、show的实现封装在FragmentManagerImpl类的hideFragment和showFragment方法中,其核心实现是修改Fragment的显示状态,通过修改Fragment的mHidden字段
replace的实现封装在BackStackRecord中expandOps方法的switch的OP_REPLACE分支中,其实现为:
- 从后往前遍历ArrayList<Fragment>中的Fragment,查找是否有mContainerId与添加Fragment一致的
- 若没有,则直接在ArrayList<Fragment>中add
- 若有mContainerId相同
1 有相同实例,移除mOps中的该实例
2 若实例不相同,则添加到mOps中,同时remove ArrayList<Fragment>中旧的Fragment再add新的Fragment
总结
add + hide + show的实现是往ArrayList<Fragment>中add Fragment以及修改Fragment中的字段修改可见属性
replace的实现是把具有相同父容器id中的不同Fragment实例先remove再add,相同父容器中的相同实例则并没有对ArrayList<Fragment>进行任何操作,而是通过修改mOps (定义于Fragment中的ArrayList<Op> mOps = new ArrayList<>();) 来实现操作
总的来说就是,Add相当于Activtiy的标准模式,总会添加新的实例,replace的核心判断条件是父容器ID,相当于Activity的栈,遍历移除所有非当前实例的Fragment,但是被移除的Fragment会加入mOps记录(暂时作用不明),有点像Activity的栈内单例模式(不完全一样)
举个例子便于理解就是,假设一个容器ID中有5个Fragment各不相同,向其中add5个fragment中的一个就会有两个相同的fragment共六个实例
而replace则会把其他四个移除(添加到mOps中),只保留要replace的那个实例,关键在于,它没有添加新的实例
Note:目前只是分析理解了add和replace过程对Fragment容器List的管理,还没有解析FragmentManager怎么把List中的Fragment显示或加载的,后续还需要学习
这是目前的理解,随着不断学习,随时有可能修改!
网友评论