Android開發中,若是存在多個Fragment,常常能遇到以下Fragment異常,意味着該fragment 被重複add。java
java.lang.IllegalStateException: Fragment already added:xxxFragmentandroid
代碼以下緩存
public Fragment showFragment(int position, Bundle bundle) { try { if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } String fragmentTag = createFragmentName(mViewContainer.getId(), position); Fragment fragment = mFragmentManager.findFragmentByTag(fragmentTag); if (fragment == null) { fragment = instantiateItem(position); fragment.setUserVisibleHint(false); } if (mCurrentPrimaryItem != fragment) { //防止重複add if (mCurrentPrimaryItem != null) { mCurTransaction.hide(mCurrentPrimaryItem); mCurrentPrimaryItem.setUserVisibleHint(false); } if (fragment.isAdded()) { mCurTransaction.show(fragment); } else{ mCurTransaction.add(mViewContainer.getId(), fragment, fragmentTag); } mCurrentPrimaryItem = fragment; } if(bundle!=null) setArgs(bundle); if (!mCurrentPrimaryItem.getUserVisibleHint()) { mCurrentPrimaryItem.setUserVisibleHint(true); } mCurTransaction.commit(); return fragment; } catch (Exception e) { e.printStackTrace(); } return null; }
解決問題的前提每每是分析問題,那麼這種問題是如何致使的呢?app
在FragmentTransaction中,咱們經常使用的是add和attach方法來添加fragment,這2個方法中的動做並不會當即執行,而是將OP任務加入了本身的隊列。OP任務在等待commit系列方法提交事務以後執行,但同時commit 方法提交的任務加入到了主線程Looper中,若是Looper阻塞,add OP可能會延遲。ide
所以,這種狀況下致使屢次點擊tab切換不相鄰的fragment的時候if (mCurrentPrimaryItem != fragment) 條件可能成立,那麼接下來就會調用isAdd()方法 ,但isAdd()方法可能返回的是false ,由於只有Fragment被真正add以後才返回true,但同時findFragmentByTag卻能返回當前的fragment示例。oop
新版FragmentTransaction提供了commitNow方法,這個方法被調用以後,任務不會被加入主線Looper,能夠當即執行spa
使用場景:解決數量較小(數量在4之內)和UI和Work相對簡單Fragment的add問題,若是是複雜Fragment或者數量較多的Fragment被add,有可能致使卡頓、ANR問題.線程
mCurTransaction.commitNow();
此外,commitNow沒法與addToBackStack並用,由於該方法內部使用了disallowAddToBackStack(),若是調用addToBackStack()會發生異常code
commit把OP ADD任務加入到Looper隊列中,而且是MainLooper隊列,因爲隊列是先進先出的關係,所以,咱們在OP ADD爲完成以前,進行攔截。完成以後刪除緩存隊列
final Map<android.app.FragmentManager, RequestManagerFragment> pendingRequestManagerFragments = new HashMap<>(); private BaseFragment getFragment(@NonNull FragmentManager fm,String FragmentTAG) { BaseFragment current = (BaseFragment) fm.findFragmentByTag(FragmentTAG); if (current == null) { current = pendingRequestManagerFragments.get(fm); //若是存在,則表示OP ADD未完成 if (current == null) { current = new MyBaseFragment(); pendingRequestManagerFragments.put(fm, current); fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss(); handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget(); } } return current; } private final Handler handler = new Handler(Looper.getMainLooper(),new Callback(){ @Override public boolean handleMessage(Message message) { boolean handled = true; Object removed = null; Object key = null; switch (message.what) { case ID_REMOVE_FRAGMENT_MANAGER: android.app.FragmentManager fm = (android.app.FragmentManager) message.obj; key = fm; removed = pendingRequestManagerFragments.remove(fm); break; case ID_REMOVE_SUPPORT_FRAGMENT_MANAGER: FragmentManager supportFm = (FragmentManager) message.obj; key = supportFm; removed = pendingSupportRequestManagerFragments.remove(supportFm); break; default: handled = false; break; } if (handled && removed == null && Log.isLoggable(TAG, Log.WARN)) { Log.w(TAG, "Failed to remove expected request manager fragment, manager: " + key); } return handled; } });
使用場景:很是適合數量多,功能複雜的Fragment的事務操做
fragment在FragmentTransaction的add(fragment) 方法被調用以後,會當即賦值fragment.mFragmentManager賦值爲當前的FragmentManager,所以改造方式能夠以下:
if (fragment.isAdded()) { mCurTransaction.show(fragment); } else if(fragment.getFragmentManager()!=null){ //防止fragment被屢次加載 mCurTransaction.show(fragment); }else{ mCurTransaction.add(mViewContainer.getId(), fragment, fragmentTag); }
使用場景:比較適合fragment數量爲2個的fragment切換
可是這種方式也存在一個問題,若是涉及頻繁的show與hide的切換不一樣步問題,在MinLooper中OP hide以後可能OP show了。