fragment中的add和replace方法的區(qū)別淺析
使用 FragmentTransaction 的時(shí)候,它提供了這樣兩個(gè)方法,一個(gè) add , 一個(gè) replace ,對(duì)這兩個(gè)方法的區(qū)別一直有點(diǎn)疑惑。
我覺得使用 add 的話,在按返回鍵應(yīng)該是回退到上一個(gè) Fragment,而使用 replace 的話,那個(gè)別 replace 的就已經(jīng)不存在了,所以就不會(huì)回退了。但事實(shí)不是這樣子的。add 和 replace 影響的只是界面,而控制回退的,是事務(wù)。
public abstract FragmentTransaction add (int containerViewId, Fragment fragment, String tag) Add a fragment to the activity state. This fragment may optionally also have its view (if Fragment.onCreateView returns non-null) into a Container view of the activity.
add 是把一個(gè)fragment添加到一個(gè)容器 container 里。
public abstract FragmentTransaction replace (int containerViewId, Fragment fragment, String tag) Replace an existing fragment that was added to a container. This is essentially the same as calling remove(Fragment) for all currently added fragments that were added with the same containerViewId and then add(int, Fragment, String) with the same arguments given here.
replace 是先remove掉相同id的所有fragment,然后在add當(dāng)前的這個(gè)fragment。
在大部分情況下,這兩個(gè)的表現(xiàn)基本相同。因?yàn)椋话悖蹅儠?huì)使用一個(gè)FrameLayout來當(dāng)容器,而每個(gè)Fragment被add 或者 replace 到這個(gè)FrameLayout的時(shí)候,都是顯示在最上層的。所以你看到的界面都是一樣的。但是,使用add的情況下,這個(gè)FrameLayout其實(shí)有2層,多層肯定要比一層的來得浪費(fèi),所以還是推薦使用replace。當(dāng)然有時(shí)候還是需要使用add的。比如要實(shí)現(xiàn)輪播圖的效果,每個(gè)輪播圖都是一個(gè)獨(dú)立的Fragment,而他的容器FrameLayout需要add多個(gè)Fragment,這樣他就可以根據(jù)提供的邏輯進(jìn)行輪播了。
而至于返回鍵的時(shí)候,這個(gè)跟事務(wù)有關(guān),跟使用add還是replace沒有任何關(guān)系。
2015.08.04 更新。
發(fā)現(xiàn)這篇博文被搜索得挺多的,上面是分析是在官方文檔上的基礎(chǔ)上加上一些個(gè)人的猜測(cè),為了避免誤人子弟,下面從代碼實(shí)現(xiàn)的角度做了些分析。希望能幫到大家,也煩請(qǐng)大家在轉(zhuǎn)載的同時(shí)注明出處,畢竟寫這么一篇博文確實(shí)很不容易(binkery)。
Android Fragment 和 FragmentManager 的代碼分析 這篇文章也是從代碼的角度分析了 FragmentManager 的工作機(jī)制。
FragmentManager 是一個(gè)抽象類,實(shí)現(xiàn)類是 FragmentManagerImpl ,跟 FragmentManager 在同一個(gè)類文件里。FragmentTransaction 也是一個(gè)抽象類,具體實(shí)現(xiàn)是 BackStackRecord 。BackStackRecord 其實(shí)是一個(gè)封裝了一個(gè)隊(duì)列。咱們看 add 方法和 replace 方法。
add 方法和 replace 方法都是把一個(gè)操作 OP_XX 放入到隊(duì)列里,Op 是其內(nèi)部封裝的一個(gè)操作的類。在 BackStackRecord 的 run 方法里,每次會(huì)從隊(duì)列的頭(mHead)獲取一個(gè)操作 Op ,如果 Op 操作是 add ,則調(diào)用 FragmentManager 的 addFragment() 方法,如果 Op 操作是 replace ,則先調(diào)用 FragmentManager 的 removeFragment() 方法,然后再調(diào)用 addFragment() 方法。
下面是 add 方法。
public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) { doAddOp(containerViewId, fragment, tag, OP_ADD); return this; }
下面是 replace 方法。
public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) { if (containerViewId == 0) { throw new IllegalArgumentException("Must use non-zero containerViewId"); } doAddOp(containerViewId, fragment, tag, OP_REPLACE); return this; }
add 和 replace 方法都是調(diào)用的 doAddOp 方法。也就是把一個(gè)操作 Op 添加到隊(duì)列。
private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) { fragment.mFragmentManager = mManager; if (tag != null) { if (fragment.mTag != null && !tag.equals(fragment.mTag)) { throw new IllegalStateException("Can't change tag of fragment " + fragment + ": was " + fragment.mTag + " now " + tag); } fragment.mTag = tag; } if (containerViewId != 0) { if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) { throw new IllegalStateException("Can't change container ID of fragment " + fragment + ": was " + fragment.mFragmentId + " now " + containerViewId); } fragment.mContainerId = fragment.mFragmentId = containerViewId; } Op op = new Op(); op.cmd = opcmd; op.fragment = fragment; addOp(op); }
run 方法才是真正執(zhí)行的方法。什么時(shí)候執(zhí)行先不考慮,只需要知道一系列的操作會(huì)一次執(zhí)行,而不是一個(gè)操作執(zhí)行一次。
run 方法有點(diǎn)大,就看一下 while 循環(huán)開始和結(jié)束的時(shí)候,以及 switch case 里 OP_ADD 和 OP_REPLACE 分支就可以了。
public void run() { if (FragmentManagerImpl.DEBUG) { Log.v(TAG, "Run: " + this); } if (mAddToBackStack) { if (mIndex < 0) { throw new IllegalStateException("addToBackStack() called after commit()"); } } bumpBackStackNesting(1); SparseArray<Fragment> firstOutFragments = new SparseArray<Fragment>(); SparseArray<Fragment> lastInFragments = new SparseArray<Fragment>(); calculateFragments(firstOutFragments, lastInFragments); beginTransition(firstOutFragments, lastInFragments, false); // 獲取隊(duì)列的頭 Op op = mHead; while (op != null) { switch (op.cmd) { case OP_ADD: { Fragment f = op.fragment; f.mNextAnim = op.enterAnim; mManager.addFragment(f, false);//添加 } break; case OP_REPLACE: { Fragment f = op.fragment; if (mManager.mAdded != null) { for (int i = 0; i < mManager.mAdded.size(); i++) { Fragment old = 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 = new ArrayList<Fragment>(); } op.removed.add(old); old.mNextAnim = op.exitAnim; if (mAddToBackStack) { old.mBackStackNesting += 1; if (FragmentManagerImpl.DEBUG) { Log.v(TAG, "Bump nesting of " + old + " to " + old.mBackStackNesting); } } mManager.removeFragment(old, mTransition, mTransitionStyle);//刪除 } } } } if (f != null) { f.mNextAnim = op.enterAnim; mManager.addFragment(f, false);//添加 } } break; case OP_REMOVE: { Fragment f = op.fragment; f.mNextAnim = op.exitAnim; mManager.removeFragment(f, mTransition, mTransitionStyle); } break; case OP_HIDE: { Fragment f = op.fragment; f.mNextAnim = op.exitAnim; mManager.hideFragment(f, mTransition, mTransitionStyle); } break; case OP_SHOW: { Fragment f = op.fragment; f.mNextAnim = op.enterAnim; mManager.showFragment(f, mTransition, mTransitionStyle); } break; case OP_DETACH: { Fragment f = op.fragment; f.mNextAnim = op.exitAnim; mManager.detachFragment(f, mTransition, mTransitionStyle); } break; case OP_ATTACH: { Fragment f = op.fragment; f.mNextAnim = op.enterAnim; mManager.attachFragment(f, mTransition, mTransitionStyle); } break; default: { throw new IllegalArgumentException("Unknown cmd: " + op.cmd); } } op = op.next;//隊(duì)列的下一個(gè) } mManager.moveToState(mManager.mCurState, mTransition, mTransitionStyle, true); if (mAddToBackStack) { mManager.addBackStackState(this); } }
BackStackRecord 的構(gòu)造器里參數(shù)列表里有一個(gè) FragmentManager ,所有 BackStackRecord 其實(shí)是有一個(gè) FragmentManager 的引用的,BackStackRecord 可以直接調(diào)用 FragmentManager 的 addFragment 方法。
下面是 FragmentManager 的 addFragment() 方法,每次 add 一個(gè) Fragment,F(xiàn)ragment 對(duì)象都會(huì)被放入到 mAdded 的容器里。
public void addFragment(Fragment fragment, boolean moveToStateNow) { if (mAdded == null) { mAdded = new ArrayList<Fragment>(); } if (DEBUG) Log.v(TAG, "add: " + fragment); makeActive(fragment); if (!fragment.mDetached) { if (mAdded.contains(fragment)) { throw new IllegalStateException("Fragment already added: " + fragment); } mAdded.add(fragment); fragment.mAdded = true; fragment.mRemoving = false; if (fragment.mHasMenu && fragment.mMenuVisible) { mNeedMenuInvalidate = true; } if (moveToStateNow) { moveToState(fragment); } } }
有時(shí)候,咱們 add Fragment A, 然后 add Fragment B,B 把 A 都覆蓋了,點(diǎn)擊菜單的時(shí)候 A 和 B 的菜單選項(xiàng)都出來了,這是為什么?原因在下面。當(dāng)在創(chuàng)建 OptionsMenu 的時(shí)候,F(xiàn)ragmentManager 遍歷了 mAdded 容器,所以 A 和 B 的菜單都被添加進(jìn)來了。也就是說使用 add 的方式,雖然 B 把 A 覆蓋住了,但是 A 還是存活的,而且是活動(dòng)著的。
public boolean dispatchCreateOptionsMenu(Menu menu, MenuInflater inflater) { boolean show = false; ArrayList<Fragment> newMenus = null; if (mAdded != null) { for (int i=0; i<mAdded.size(); i++) { Fragment f = mAdded.get(i); if (f != null) { if (f.performCreateOptionsMenu(menu, inflater)) { show = true; if (newMenus == null) { newMenus = new ArrayList<Fragment>(); } newMenus.add(f); } } } } if (mCreatedMenus != null) { for (int i=0; i<mCreatedMenus.size(); i++) { Fragment f = mCreatedMenus.get(i); if (newMenus == null || !newMenus.contains(f)) { f.onDestroyOptionsMenu(); } } } mCreatedMenus = newMenus; return show; }
小結(jié):fragment中的add和replace方法的區(qū)別
使用add方法時(shí),需要考慮fragment引用被清空的情況。
使用add方法add到activity里面的fragment的對(duì)象并不會(huì)被銷毀。也就是它任然在activity中存在,只是應(yīng)用被置為null而已。此時(shí)如果重新為fragment賦值,其hide方法和show方法都不會(huì)生效。如果這種情況下,一個(gè)activity中有多個(gè)fragment,很可能出現(xiàn)多個(gè)fragment層疊而不能正常的顯示或者隱藏。
使用add方法使用的fragment的優(yōu)點(diǎn)在于它占用內(nèi)存資源少,通過replace方法使用fragment占用資源雖然會(huì)多一些,但是不存在add方法的bug。
所以開發(fā)的時(shí)候,盡量處理好add方法可能引起的bug。
fragment還要處理好commit和transaction.commitAllowingStateLoss()兩個(gè)方法。
以上所述是小編給大家介紹的fragment中的add和replace方法的區(qū)別淺析,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
Android編程之界面實(shí)現(xiàn)全屏顯示的方法(2種方法)
這篇文章主要介紹了Android編程之界面實(shí)現(xiàn)全屏顯示的方法,結(jié)合實(shí)例分析了Java代碼中設(shè)置與Manifest文件設(shè)置2種實(shí)現(xiàn)方法,需要的朋友可以參考下2016-01-01Android添加(創(chuàng)建)、刪除及判斷是否存在桌面快捷方式的方法
這篇文章主要介紹了Android添加(創(chuàng)建)、刪除及判斷是否存在桌面快捷方式的方法,涉及Android針對(duì)桌面快捷方式的相關(guān)操作技巧,需要的朋友可以參考下2015-05-05Android 6.0 藍(lán)牙搜索不到設(shè)備原因,MIUI權(quán)限申請(qǐng)機(jī)制方法
今天小編就為大家分享一篇Android6.0 藍(lán)牙搜索不到設(shè)備原因,MIUI權(quán)限申請(qǐng)機(jī)制方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-07-07Android Material設(shè)計(jì)中列表和卡片的創(chuàng)建方法解析
這篇文章主要介紹了Android Material設(shè)計(jì)中列表和卡片的創(chuàng)建方法解析,列表和卡片是Material Design視圖中的重要部分,需要的朋友可以參考下2016-04-04Android如何調(diào)用系統(tǒng)相機(jī)拍照
這篇文章主要為大家詳細(xì)介紹了Android如何調(diào)用系統(tǒng)相機(jī)拍照的相關(guān)代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09Android AutoCompleteTextView自動(dòng)提示文本框?qū)嵗a
這篇文章主要介紹了Android AutoCompleteTextView自動(dòng)提示文本框?qū)嵗a的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-07-07android中ListView數(shù)據(jù)刷新時(shí)的同步方法
這篇文章主要介紹了android中ListView數(shù)據(jù)刷新時(shí)的同步方法,涉及Android刷新listview實(shí)現(xiàn)數(shù)據(jù)同步的技巧,需要的朋友可以參考下2015-05-05解析Android應(yīng)用程序運(yùn)行機(jī)制
這篇文章主要介紹了Android應(yīng)用程序運(yùn)行機(jī)制,有需要的朋友可以參考一下2014-01-01