fragment中的add和replace方法的区别浅析
使用FragmentTransaction的时候,它提供了这样两个方法,一个add,一个replace,对这两个方法的区别一直有点疑惑。
我觉得使用add的话,在按返回键应该是回退到上一个Fragment,而使用replace的话,那个别replace的就已经不存在了,所以就不会回退了。但事实不是这样子的。add和replace影响的只是界面,而控制回退的,是事务。
publicabstractFragmentTransactionadd(intcontainerViewId,Fragmentfragment,Stringtag) Addafragmenttotheactivitystate.Thisfragmentmayoptionallyalsohaveitsview(ifFragment.onCreateViewreturnsnon-null)intoaContainerviewoftheactivity.
add是把一个fragment添加到一个容器container里。
publicabstractFragmentTransactionreplace(intcontainerViewId,Fragmentfragment,Stringtag) Replaceanexistingfragmentthatwasaddedtoacontainer.Thisisessentiallythesameascallingremove(Fragment)forallcurrentlyaddedfragmentsthatwereaddedwiththesamecontainerViewIdandthenadd(int,Fragment,String)withthesameargumentsgivenhere.
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)。
AndroidFragment和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方法。
publicFragmentTransactionadd(intcontainerViewId,Fragmentfragment,Stringtag){
doAddOp(containerViewId,fragment,tag,OP_ADD);
returnthis;
}
下面是replace方法。
publicFragmentTransactionreplace(intcontainerViewId,Fragmentfragment,Stringtag){
if(containerViewId==0){
thrownewIllegalArgumentException("Mustusenon-zerocontainerViewId");
}
doAddOp(containerViewId,fragment,tag,OP_REPLACE);
returnthis;
}
add和replace方法都是调用的doAddOp方法。也就是把一个操作Op添加到队列。
privatevoiddoAddOp(intcontainerViewId,Fragmentfragment,Stringtag,intopcmd){
fragment.mFragmentManager=mManager;
if(tag!=null){
if(fragment.mTag!=null&&!tag.equals(fragment.mTag)){
thrownewIllegalStateException("Can'tchangetagoffragment"
+fragment+":was"+fragment.mTag
+"now"+tag);
}
fragment.mTag=tag;
}
if(containerViewId!=0){
if(fragment.mFragmentId!=0&&fragment.mFragmentId!=containerViewId){
thrownewIllegalStateException("Can'tchangecontainerIDoffragment"
+fragment+":was"+fragment.mFragmentId
+"now"+containerViewId);
}
fragment.mContainerId=fragment.mFragmentId=containerViewId;
}
Opop=newOp();
op.cmd=opcmd;
op.fragment=fragment;
addOp(op);
}
run方法才是真正执行的方法。什么时候执行先不考虑,只需要知道一系列的操作会一次执行,而不是一个操作执行一次。
run方法有点大,就看一下while循环开始和结束的时候,以及switchcase里OP_ADD和OP_REPLACE分支就可以了。
publicvoidrun(){
if(FragmentManagerImpl.DEBUG){
Log.v(TAG,"Run:"+this);
}
if(mAddToBackStack){
if(mIndex<0){
thrownewIllegalStateException("addToBackStack()calledaftercommit()");
}
}
bumpBackStackNesting(1);
SparseArray<Fragment>firstOutFragments=newSparseArray<Fragment>();
SparseArray<Fragment>lastInFragments=newSparseArray<Fragment>();
calculateFragments(firstOutFragments,lastInFragments);
beginTransition(firstOutFragments,lastInFragments,false);
//获取队列的头
Opop=mHead;
while(op!=null){
switch(op.cmd){
caseOP_ADD:{
Fragmentf=op.fragment;
f.mNextAnim=op.enterAnim;
mManager.addFragment(f,false);//添加
}
break;
caseOP_REPLACE:{
Fragmentf=op.fragment;
if(mManager.mAdded!=null){
for(inti=0;i<mManager.mAdded.size();i++){
Fragmentold=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=newArrayList<Fragment>();
}
op.removed.add(old);
old.mNextAnim=op.exitAnim;
if(mAddToBackStack){
old.mBackStackNesting+=1;
if(FragmentManagerImpl.DEBUG){
Log.v(TAG,"Bumpnestingof"
+old+"to"+old.mBackStackNesting);
}
}
mManager.removeFragment(old,mTransition,mTransitionStyle);//删除
}
}
}
}
if(f!=null){
f.mNextAnim=op.enterAnim;
mManager.addFragment(f,false);//添加
}
}
break;
caseOP_REMOVE:{
Fragmentf=op.fragment;
f.mNextAnim=op.exitAnim;
mManager.removeFragment(f,mTransition,mTransitionStyle);
}
break;
caseOP_HIDE:{
Fragmentf=op.fragment;
f.mNextAnim=op.exitAnim;
mManager.hideFragment(f,mTransition,mTransitionStyle);
}
break;
caseOP_SHOW:{
Fragmentf=op.fragment;
f.mNextAnim=op.enterAnim;
mManager.showFragment(f,mTransition,mTransitionStyle);
}
break;
caseOP_DETACH:{
Fragmentf=op.fragment;
f.mNextAnim=op.exitAnim;
mManager.detachFragment(f,mTransition,mTransitionStyle);
}
break;
caseOP_ATTACH:{
Fragmentf=op.fragment;
f.mNextAnim=op.enterAnim;
mManager.attachFragment(f,mTransition,mTransitionStyle);
}
break;
default:{
thrownewIllegalArgumentException("Unknowncmd:"+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的容器里。
publicvoidaddFragment(Fragmentfragment,booleanmoveToStateNow){
if(mAdded==null){
mAdded=newArrayList<Fragment>();
}
if(DEBUG)Log.v(TAG,"add:"+fragment);
makeActive(fragment);
if(!fragment.mDetached){
if(mAdded.contains(fragment)){
thrownewIllegalStateException("Fragmentalreadyadded:"+fragment);
}
mAdded.add(fragment);
fragment.mAdded=true;
fragment.mRemoving=false;
if(fragment.mHasMenu&&fragment.mMenuVisible){
mNeedMenuInvalidate=true;
}
if(moveToStateNow){
moveToState(fragment);
}
}
}
有时候,咱们addFragmentA,然后addFragmentB,B把A都覆盖了,点击菜单的时候A和B的菜单选项都出来了,这是为什么?原因在下面。当在创建OptionsMenu的时候,FragmentManager遍历了mAdded容器,所以A和B的菜单都被添加进来了。也就是说使用add的方式,虽然B把A覆盖住了,但是A还是存活的,而且是活动着的。
publicbooleandispatchCreateOptionsMenu(Menumenu,MenuInflaterinflater){
booleanshow=false;
ArrayList<Fragment>newMenus=null;
if(mAdded!=null){
for(inti=0;i<mAdded.size();i++){
Fragmentf=mAdded.get(i);
if(f!=null){
if(f.performCreateOptionsMenu(menu,inflater)){
show=true;
if(newMenus==null){
newMenus=newArrayList<Fragment>();
}
newMenus.add(f);
}
}
}
}
if(mCreatedMenus!=null){
for(inti=0;i<mCreatedMenus.size();i++){
Fragmentf=mCreatedMenus.get(i);
if(newMenus==null||!newMenus.contains(f)){
f.onDestroyOptionsMenu();
}
}
}
mCreatedMenus=newMenus;
returnshow;
}
小结: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方法的区别浅析,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!