记一次FragmentManager动态添加fragment导致的重复调用生命周期的问题

1,157 阅读2分钟

当使用FragmentManager,对fragment作出操作,执行commit方法时,先会对生成的BackStackRecord进行检查是否有添加,而在enqueue方法中会通过handler机制,发送一个名叫mExecCommit的Runnable对象,将事件发送到添加到主线程中进行执行。

int commitInternal(boolean allowStateLoss) {
        if (mCommitted) throw new IllegalStateException("commit already called");
        ...
        mCommitted = true;
        if (mAddToBackStack) {
            mIndex = mManager.allocBackStackIndex(this);
        } else {
            mIndex = -1;
        }
        mManager.enqueueAction(this, allowStateLoss);
        return mIndex;
    }

而对于fragment的操作会转化为一个或多个BackStackRecord中的mOps中的Op对象(如replace操作就会生成两个Op对象,每个Op对象会对应到一个FragmentManagerImpl的一个操作,包括addFragment,removeFragment,hideFragment,showFragment,detachFragment,attachFragment)。经过一系列处理后,最终会调用到FragmentManagerImpl的addFragment等方法把view与viewTree产生联系。 而在FragmentManagerImpl的addFragment方法中会检查要加入的Fragment是否已经加入过了。

public void addFragment(Fragment fragment, boolean moveToStateNow) {
        makeActive(fragment);
        if (!fragment.mDetached) {
            if (mAdded.contains(fragment)) {
                throw new IllegalStateException("Fragment already added: " + fragment);
            }
            synchronized (mAdded) {
                mAdded.add(fragment);
            }
            fragment.mAdded = true;
            fragment.mRemoving = false;
            if (fragment.mView == null) {
                fragment.mHiddenChanged = false;
            }
            if (fragment.mHasMenu && fragment.mMenuVisible) {
                mNeedMenuInvalidate = true;
            }
            if (moveToStateNow) {
                moveToState(fragment);
            }
        }
    }

当messageQueue中的mExecCommit执行的时,目标BackStackRecord对象的mReorderingAllowed为true,会对添加了的其他Fragment调整生命周期。

在Activity继承的FragmentActivityh每个生命周期中会有一些调用来调用添加了的Fragment的生命周期。来保持fragment的生命周期和activity的生命周期一致。

//FragmentActivity
@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        mFragments.attachHost(null /*parent*/);

        super.onCreate(savedInstanceState);
        ...

        mFragments.dispatchCreate();
    }

这里会调用到FragmentManagerImpl的moveToState方法,挨个的改变fragment的生命周期。

final ArrayList<Fragment> mAdded = new ArrayList<>();

void moveToState(int newState, boolean always) {
    ...

    mCurState = newState;

    if (mActive != null) {

        // Must add them in the proper order. mActive fragments may be out of order
        final int numAdded = mAdded.size();
        for (int i = 0; i < numAdded; i++) {
            Fragment f = mAdded.get(i);
            moveFragmentToExpectedState(f);
        }

    }
}

若在activity的每一次生命周期中调用FragmentManager的诸如replace方法,调用后会向主线程的looper发送一个Runnable,这个runnable执行的时候会对你的目标fragment/所有添加乐的fragment的生命周期进行处理。

例如,在activity的onCreate方法里写入getSupportFragmentManager().beginTransaction().replace(R.id.xxx,fragment).commit()。这时你的fragment还在FragmentManagerd的mAdded之中,当出现activity的生命周期变化时,这个fragment的生命周期也会跟着改变,在activity的销毁中没有问题。而如果activity要重建(例如分屏操作),执行到onCreate写入的方法,会先向主线程的looper中发送一个Runnable对象,并且继续执行生命周期,当looper执行到发送的Runnable后,会先调用FragmentManagerImpl的removeFragment,而后再调用addFregment。而这里的两个调用都会依次fragment的生命周期。

而这里的fragment又是同一个,此时就会出现fragment的生命周期被调用两次的情况,。而如果不触发activity的重建则不会出现这种情况。