Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> fragment中的add和replace方法的區別淺析

fragment中的add和replace方法的區別淺析

編輯:關於Android編程

使用 FragmentTransaction 的時候,它提供了這樣兩個方法,一個 add , 一個 replace ,對這兩個方法的區別一直有點疑惑。

我覺得使用 add 的話,在按返回鍵應該是回退到上一個 Fragment,而使用 replace 的話,那個別 replace 的就已經不存在了,所以就不會回退了。但事實不是這樣子的。add 和 replace 影響的只是界面,而控制回退的,是事務。

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 是把一個fragment添加到一個容器 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當前的這個fragment。

在大部分情況下,這兩個的表現基本相同。因為,一般,咱們會使用一個FrameLayout來當容器,而每個Fragment被add 或者 replace 到這個FrameLayout的時候,都是顯示在最上層的。所以你看到的界面都是一樣的。但是,使用add的情況下,這個FrameLayout其實有2層,多層肯定要比一層的來得浪費,所以還是推薦使用replace。當然有時候還是需要使用add的。比如要實現輪播圖的效果,每個輪播圖都是一個獨立的Fragment,而他的容器FrameLayout需要add多個Fragment,這樣他就可以根據提供的邏輯進行輪播了。

而至於返回鍵的時候,這個跟事務有關,跟使用add還是replace沒有任何關系。

2015.08.04 更新。

發現這篇博文被搜索得挺多的,上面是分析是在官方文檔上的基礎上加上一些個人的猜測,為了避免誤人子弟,下面從代碼實現的角度做了些分析。希望能幫到大家,也煩請大家在轉載的同時注明出處,畢竟寫這麼一篇博文確實很不容易(binkery)。

Android Fragment 和 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 方法。

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 方法都是調用的 doAddOp 方法。也就是把一個操作 Op 添加到隊列。

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 方法才是真正執行的方法。什麼時候執行先不考慮,只需要知道一系列的操作會一次執行,而不是一個操作執行一次。
run 方法有點大,就看一下 while 循環開始和結束的時候,以及 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);
 // 獲取隊列的頭
 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;//隊列的下一個
 }
 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 的容器裡。

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);
 }
 }
}

有時候,咱們 add Fragment A, 然後 add Fragment B,B 把 A 都覆蓋了,點擊菜單的時候 A 和 B 的菜單選項都出來了,這是為什麼?原因在下面。當在創建 OptionsMenu 的時候,FragmentManager 遍歷了 mAdded 容器,所以 A 和 B 的菜單都被添加進來了。也就是說使用 add 的方式,雖然 B 把 A 覆蓋住了,但是 A 還是存活的,而且是活動著的。

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;
}

小結: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方法的區別淺析,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對本站網站的支持!

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved