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