Android Fragment重复添加问题解决方法

释放双眼,带上耳机,听听看~!

情景说明

Android开发中,如果存在多个Fragment,经常能遇到如下Fragment异常,意味着该fragment 被重复add。

java.lang.IllegalStateException: Fragment already added:xxxFragment

 

代码如下

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

 

思路分析

解决问题的前提往往是分析问题,那么这种问题是如何导致的呢?

在FragmentTransaction中,我们常用的是add和attach方法来添加fragment,这2个方法中的动作并不会立即执行,而是将OP任务加入了自己的队列。OP任务在等待commit系列方法提交事务之后执行,但同时commit 方法提交的任务加入到了主线程Looper中,如果Looper阻塞,add OP可能会延迟。

 因此,这种情况下导致多次点击tab切换不相邻的fragment的时候if (mCurrentPrimaryItem != fragment) 条件可能成立,那么接下来就会调用isAdd()方法 ,但isAdd()方法可能返回的是false ,因为只有Fragment被真正add之后才返回true,但同时findFragmentByTag却能返回当前的fragment示例。

 

解决方法

方法一,commitNow系列

新版FragmentTransaction提供了commitNow方法,这个方法被调用之后,任务不会被加入主线Looper,可以立即执行

使用场景:解决数量较小(数量在4以内)和UI和Work相对简单Fragment的add问题,如果是复杂Fragment或者数量较多的Fragment被add,有可能导致卡顿、ANR问题.

mCurTransaction.commitNow();

此外,commitNow无法与addToBackStack并用,因为该方法内部使用了disallowAddToBackStack(),如果调用addToBackStack()会发生异常

方法二、使用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的事务操作

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

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

 

人已赞赏
Android文章

Android 自动化埋点方案

2019-10-26 15:31:41

Android文章

Android ProGuard 代码混淆

2019-10-27 4:34:28

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
有新消息 消息中心
搜索