Android Fragment重複添加問題解決方法

情景說明

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

 

解決方法

方法一,commitNow系列

新版FragmentTransaction提供了commitNow方法,這個方法被調用以後,任務不會被加入主線Looper,能夠當即執行spa

使用場景:解決數量較小(數量在4之內)和UI和Work相對簡單Fragment的add問題,若是是複雜Fragment或者數量較多的Fragment被add,有可能致使卡頓、ANR問題.線程

mCurTransaction.commitNow();

此外,commitNow沒法與addToBackStack並用,由於該方法內部使用了disallowAddToBackStack(),若是調用addToBackStack()會發生異常code

方法2、使用Looper隊列

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的事務操做

方法3、改造流程控制方式

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

相關文章
相關標籤/搜索