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了。