Android进阶宝典 -- 从源码角度全面分析Frgament原理

语言: CN / TW / HK

在Android中,真正作为承载页面级别的组件就两个:Activity和Fragment。说起Activity,我想伙伴们都非常熟悉,如果想新建一个页面,那么就创建一个Activity,这个是传统的开发思想。

伙伴们可以想一想,当一个项目成规模以后,Activity的量级达到了50+ or 100+,这个时候页面之间的跳转就需要路由来管理,如果想写startActivity写到吐,那么也没问题。那么有没有好的方式能够非常方便地管理页面,而且能够用最少量的Activity,建议伙伴们去了解下Navigation,其内部有一套Fragment的管理机制。

image.png

为啥要出这篇Fragment核心原理分析,就是因为在项目中使用Navigation的时候遇到了一些问题,但是伙伴们可能对于Fragment的原理不是那么了解,像生命周期、Fragment事务管理、回退栈等,那么在使用Navigation的时候,如果想要Hook Navigation源码,不知道如何处理Fragment之间的跳转逻辑,那么看了这篇文章,可能会有所帮助。

1 Fragment基础概念

1.1 Fragment的生命周期

首先我们先要知道,Fragment是不能像Activity那样独立存在,你可以认为它是一个View,它必须要依赖于Activity存在,而且是受Activity的生命周期影响,从而改变自身的生命周期,反之它没有影响Activity生命周期的能力。

那么我们从源码的角度看一下,Activity是如何影响Fragment的生命周期的,然后从实例出发验证一下与源码是否一致。那么我们从FragmentActivity的onCreate方法开始,当Activity调用onCreate方法的时候,就会回调这个方法。

```java @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState);

mFragmentLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
mFragments.dispatchCreate();

} ```

首先我们看一下,mFragments是什么,它是一个FragmentController,从字面意思上看,是Fragment的一个控制器 java final FragmentController mFragments = FragmentController.createController(new HostCallbacks()); 当Activity执行onCreate方法的时候,最终是调用FragmentManager的dispatchCreate方法 java public void dispatchStart() { mHost.mFragmentManager.dispatchStart(); } java void dispatchCreate() { mStateSaved = false; mStopped = false; mNonConfig.setIsStateSaved(false); // 核心代码 - 1 dispatchStateChange(Fragment.CREATED); }

核心代码 - 1

我们看下Fragment的生命周期是如何发生变化的,首先在dispatchStateChange方法中,会传入一个int值, java private void dispatchStateChange(int nextState) { try { mExecutingActions = true; mFragmentStore.dispatchStateChange(nextState); moveToState(nextState, false); if (USE_STATE_MANAGER) { Set<SpecialEffectsController> controllers = collectAllSpecialEffectsController(); for (SpecialEffectsController controller : controllers) { controller.forceCompleteAllOperations(); } } } finally { mExecutingActions = false; } execPendingActions(true); } 这个值从INITIALIZING -> RESUMED是升序排序,这里我们可能会有疑问,Fragment的onPause、onStop、onDestory去哪了,为啥只到了RESUMEDjava static final int INITIALIZING = -1; // Not yet attached. static final int ATTACHED = 0; // Attached to the host. static final int CREATED = 1; // Created. static final int VIEW_CREATED = 2; // View Created. static final int AWAITING_EXIT_EFFECTS = 3; // Downward state, awaiting exit effects static final int ACTIVITY_CREATED = 4; // Fully created, not started. static final int STARTED = 5; // Created and started, not resumed. static final int AWAITING_ENTER_EFFECTS = 6; // Upward state, awaiting enter effects static final int RESUMED = 7; // Created started and resumed. 别着急,我们往下看moveToState方法。 ```java void moveToState(int newState, boolean always) { if (mHost == null && newState != Fragment.INITIALIZING) { throw new IllegalStateException("No activity"); }

if (!always && newState == mCurState) {
    return;
}

mCurState = newState;

if (USE_STATE_MANAGER) {
    mFragmentStore.moveToExpectedState();
} else {
    // Must add them in the proper order. mActive fragments may be out of order
    for (Fragment f : mFragmentStore.getFragments()) {
        moveFragmentToExpectedState(f);
    }

    // Now iterate through all active fragments. These will include those that are removed
    // and detached.
    for (FragmentStateManager fragmentStateManager :
            mFragmentStore.getActiveFragmentStateManagers()) {
        Fragment f = fragmentStateManager.getFragment();
        if (!f.mIsNewlyAdded) {
            moveFragmentToExpectedState(f);
        }
        boolean beingRemoved = f.mRemoving && !f.isInBackStack();
        if (beingRemoved) {
            mFragmentStore.makeInactive(fragmentStateManager);
        }
    }
}

startPendingDeferredFragments();

if (mNeedMenuInvalidate && mHost != null && mCurState == Fragment.RESUMED) {
    mHost.onSupportInvalidateOptionsMenu();
    mNeedMenuInvalidate = false;
}

} 其中,核心方法为moveFragmentToExpectedState,传入的参数为存储的Fragment实例,最终生命周期的同步,是在moveToState方法中,此时两个参数:一个是Fragment实例,另一个是即将更新的Fragment的生命周期状态。java void moveToState(@NonNull Fragment f, int newState) { FragmentStateManager fragmentStateManager = mFragmentStore.getFragmentStateManager(f.mWho); //...... if (f.mFromLayout && f.mInLayout && f.mState == Fragment.VIEW_CREATED) { newState = Math.max(newState, Fragment.VIEW_CREATED); } newState = Math.min(newState, fragmentStateManager.computeExpectedState()); if (f.mState <= newState) { // If we are moving to the same state, we do not need to give up on the animation. if (f.mState < newState && !mExitAnimationCancellationSignals.isEmpty()) { // The fragment is currently being animated... but! Now we // want to move our state back up. Give up on waiting for the // animation and proceed from where we are. cancelExitAnimation(f); } switch (f.mState) { case Fragment.INITIALIZING: if (newState > Fragment.INITIALIZING) { fragmentStateManager.attach(); } // fall through case Fragment.ATTACHED: if (newState > Fragment.ATTACHED) { fragmentStateManager.create(); } // fall through case Fragment.CREATED: // We want to unconditionally run this anytime we do a moveToState that // moves the Fragment above INITIALIZING, including cases such as when // we move from CREATED => CREATED as part of the case fall through above. if (newState > Fragment.INITIALIZING) { fragmentStateManager.ensureInflatedView(); }

            if (newState > Fragment.CREATED) {
                fragmentStateManager.createView();
            }
            // fall through
        case Fragment.VIEW_CREATED:
            if (newState > Fragment.VIEW_CREATED) {
                fragmentStateManager.activityCreated();
            }
            // fall through
        case Fragment.ACTIVITY_CREATED:
            if (newState > Fragment.ACTIVITY_CREATED) {
                fragmentStateManager.start();
            }
            // fall through
        case Fragment.STARTED:
            if (newState > Fragment.STARTED) {
                fragmentStateManager.resume();
            }
    }
} else if (f.mState > newState) {
    switch (f.mState) {
        case Fragment.RESUMED:
            if (newState < Fragment.RESUMED) {
                fragmentStateManager.pause();
            }
            // fall through
        case Fragment.STARTED:
            if (newState < Fragment.STARTED) {
                fragmentStateManager.stop();
            }
            // fall through
        case Fragment.ACTIVITY_CREATED:
            if (newState < Fragment.ACTIVITY_CREATED) {
                if (isLoggingEnabled(Log.DEBUG)) {
                    Log.d(TAG, "movefrom ACTIVITY_CREATED: " + f);
                }
                if (f.mView != null) {
                    // Need to save the current view state if not
                    // done already.
                    if (mHost.onShouldSaveFragmentState(f) && f.mSavedViewState == null) {
                        fragmentStateManager.saveViewState();
                    }
                }
            }
            // fall through
        case Fragment.VIEW_CREATED:
            if (newState < Fragment.VIEW_CREATED) {
                FragmentAnim.AnimationOrAnimator anim = null;
                if (f.mView != null && f.mContainer != null) {
                    // Stop any current animations:
                    f.mContainer.endViewTransition(f.mView);
                    f.mView.clearAnimation();
                    // If parent is being removed, no need to handle child animations.
                    if (!f.isRemovingParent()) {
                        if (mCurState > Fragment.INITIALIZING && !mDestroyed
                                && f.mView.getVisibility() == View.VISIBLE
                                && f.mPostponedAlpha >= 0) {
                            anim = FragmentAnim.loadAnimation(mHost.getContext(),
                                    f, false, f.getPopDirection());
                        }
                        f.mPostponedAlpha = 0;
                        // Robolectric tests do not post the animation like a real device
                        // so we should keep up with the container and view in case the
                        // fragment view is destroyed before we can remove it.
                        ViewGroup container = f.mContainer;
                        View view = f.mView;
                        if (anim != null) {
                            FragmentAnim.animateRemoveFragment(f, anim,
                                    mFragmentTransitionCallback);
                        }
                        container.removeView(view);
                        if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
                            Log.v(FragmentManager.TAG, "Removing view " + view + " for "
                                    + "fragment " + f + " from container " + container);
                        }
                        // If the local container is different from the fragment
                        // container, that means onAnimationEnd was called, onDestroyView
                        // was dispatched and the fragment was already moved to state, so
                        // we should early return here instead of attempting to move to
                        // state again.
                        if (container != f.mContainer) {
                            return;
                        }
                    }
                }
                // If a fragment has an exit animation (or transition), do not destroy
                // its view immediately and set the state after animating
                if (mExitAnimationCancellationSignals.get(f) == null) {
                    fragmentStateManager.destroyFragmentView();
                }
            }
            // fall through
        case Fragment.CREATED:
            if (newState < Fragment.CREATED) {
                if (mExitAnimationCancellationSignals.get(f) != null) {
                    // We are waiting for the fragment's view to finish animating away.
                    newState = Fragment.CREATED;
                } else {
                    fragmentStateManager.destroy();
                }
            }
            // fall through
        case Fragment.ATTACHED:
            if (newState < Fragment.ATTACHED) {
                fragmentStateManager.detach();
            }
    }
}

if (f.mState != newState) {
    if (isLoggingEnabled(Log.DEBUG)) {
        Log.d(TAG, "moveToState: Fragment state for " + f + " not updated inline; "
                + "expected state " + newState + " found " + f.mState);
    }
    f.mState = newState;
}

} ``` 其实这个方法看着长,但是很简单,就是将newState与当前Fragment的状态做一次比较,如果传入的状态(newState)比当前要大,例如:

```java f.mState:CREATED -> 1 newState:VIEW_CREATED -> 2

fragmentStateManager.createView(); ``` 此时就会调用fragmentStateManager的createView方法,最终会调用Fragment的onCreateView方法,进行View的创建。

反之,如果传入的状态(newState)比当前要小,例如:

```java f.mState:RESUMED -> 7 newState:STARTED -> 5

fragmentStateManager.pause(); ``` 此时Fragment就进入了onPuse的状态,所以Google工程师在Androidx之后,将状态就限制到RESUMED,然后通过同步比较状态的这种方式,进行生命周期状态的回调。

如果看过LifeCycle的源码,对于生命周期状态的同步应该也会比较了解,感兴趣的伙伴可以看下这篇文章: Android进阶宝典 -- Jetpack篇(最新LiveData LifeCycle源码分析)

所以Activity是通过什么手段去影响Fragment生命周期的呢?就是通过FragmentController调用dispatchCreate、dispatchResume......,其实内部是通过FragmentManager来管理,通过生命周期同步的方式来主动调用Fragment的生命周期方法。

1.2 Fragment的事务管理

如果人为管理Fragment,一般都是通过Transaction进行事务管理,

```kotlin //事务管理 val beginTransaction = supportFragmentManager.beginTransaction() beginTransaction.add(R.id.fl_fg,Fragment01()) beginTransaction.replace(R.id.fl_fg,Fragment01()) beginTransaction.hide(Fragment01()) beginTransaction.show(Fragment01())

beginTransaction.commit() ```

大概分为4种操作:add、replace、hide、show;其中在使用的时候,一般是add、replace是一挂,hide和show是一挂,具体的差别我们稍后再说,我们先看下Transaction是何许人也。

java @NonNull public FragmentTransaction beginTransaction() { return new BackStackRecord(this); } 在调用beginTransaction方法的时候,其实是创建了一个BackStackRecord实例,从字面意思上看是回退栈记录类,用来记录每个Fragment的回退栈的。

那么在调用add、replace、hide、show的时候,其实就是调用BackStackRecord的方法,我们看下这几个方法的实现。

java @NonNull public FragmentTransaction add(@IdRes int containerViewId, @NonNull Fragment fragment) { doAddOp(containerViewId, fragment, null, OP_ADD); return this; } 在调用add的时候,内部调用了doAddOp方法。 ```java void doAddOp(int containerViewId, Fragment fragment, @Nullable String tag, int opcmd) { final Class<?> fragmentClass = fragment.getClass(); final int modifiers = fragmentClass.getModifiers(); if (fragmentClass.isAnonymousClass() || !Modifier.isPublic(modifiers) || (fragmentClass.isMemberClass() && !Modifier.isStatic(modifiers))) { throw new IllegalStateException("Fragment " + fragmentClass.getCanonicalName() + " must be a public static class to be properly recreated from" + " instance state."); }

if (tag != null) {
    if (fragment.mTag != null && !tag.equals(fragment.mTag)) {
        throw new IllegalStateException("Can't change tag of fragment "
                + fragment + ": was " + fragment.mTag
                + " now " + tag);
    }
    fragment.mTag = tag;
}

if (containerViewId != 0) {
    if (containerViewId == View.NO_ID) {
        throw new IllegalArgumentException("Can't add fragment "
                + fragment + " with tag " + tag + " to container view with no id");
    }
    if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
        throw new IllegalStateException("Can't change container ID of fragment "
                + fragment + ": was " + fragment.mFragmentId
                + " now " + containerViewId);
    }
    fragment.mContainerId = fragment.mFragmentId = containerViewId;
}

addOp(new Op(opcmd, fragment));

} ``` doAddOp方法前面是做了一些判断,有几个参数我要说一下:

(1)tag:这个参数可以在我们调用add的时候自定义传入,如果我们想要获取add加入的这个Fragment,可以通过findFragmentByTag方法来获取;

(2)containerViewId:这个是装载Fragment的容器,一般需要我们自行设置一个FrameLayout,取FrameLayout的id。

最后调用addOp方法,创建一个Op对象,其中Op对象中有两个参数比较重要:opcmd代表要执行的操作,例如OP_ADD(add操作)、fragment代表创建的Fragment的实例。 java void addOp(Op op) { mOps.add(op); op.mEnterAnim = mEnterAnim; op.mExitAnim = mExitAnim; op.mPopEnterAnim = mPopEnterAnim; op.mPopExitAnim = mPopExitAnim; } 创建完成之后,存放在mOps数组中。

java @NonNull public FragmentTransaction replace(@IdRes int containerViewId, @NonNull Fragment fragment, @Nullable String tag) { if (containerViewId == 0) { throw new IllegalArgumentException("Must use non-zero containerViewId"); } doAddOp(containerViewId, fragment, tag, OP_REPLACE); return this; } ```java @NonNull public FragmentTransaction hide(@NonNull Fragment fragment) { addOp(new Op(OP_HIDE, fragment));

return this;

} java @NonNull public FragmentTransaction show(@NonNull Fragment fragment) { addOp(new Op(OP_SHOW, fragment));

return this;

} ```

除此之外,我们看下replace、hide、show的逻辑,其实都是创建一个Op对象,然后存放在mOps数组中,当所有的事务准备好之后,最终需要调用commit来执行。

kotlin beginTransaction.commit() beginTransaction.commitAllowingStateLoss() beginTransaction.commitNow() beginTransaction.commitNowAllowingStateLoss() commit执行有以上四种方式,这4种方式有什么区别呢?我们从源码角度来看一下。

commit和commitAllowingStateLoss的区别

java @Override public int commit() { return commitInternal(false); } 我们看到,在commit方法执行的时候,其实是调用了commitInternal方法,传入了一个false参数,代表是否允许状态丢失。 java int commitInternal(boolean allowStateLoss) { if (mCommitted) throw new IllegalStateException("commit already called"); if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) { Log.v(TAG, "Commit: " + this); LogWriter logw = new LogWriter(TAG); PrintWriter pw = new PrintWriter(logw); dump(" ", pw); pw.close(); } mCommitted = true; if (mAddToBackStack) { mIndex = mManager.allocBackStackIndex(); } else { mIndex = -1; } mManager.enqueueAction(this, allowStateLoss); return mIndex; }

其实看到这里,我们能够猜到commitAllowingStateLoss方法调用的时候,commitInternal方法传入的一定是true,允许状态丢失。 java @Override public int commitAllowingStateLoss() { return commitInternal(true); } 在commitInternal方法中,调用了enqueueAction方法,将此次事务处理加入队列中。 java void enqueueAction(@NonNull OpGenerator action, boolean allowStateLoss) { if (!allowStateLoss) { if (mHost == null) { if (mDestroyed) { throw new IllegalStateException("FragmentManager has been destroyed"); } else { throw new IllegalStateException("FragmentManager has not been attached to a " + "host."); } } checkStateLoss(); } synchronized (mPendingActions) { if (mHost == null) { if (allowStateLoss) { // This FragmentManager isn't attached, so drop the entire transaction. return; } throw new IllegalStateException("Activity has been destroyed"); } mPendingActions.add(action); // 核心代码 - 2 scheduleCommit(); } } 在一开始就判断allowStateLoss是否为false,如果为false,也就是通过commit方式提交,那么会进入代码块,调用checkStateLoss方法。 java private void checkStateLoss() { if (isStateSaved()) { throw new IllegalStateException( "Can not perform this action after onSaveInstanceState"); } } java public boolean isStateSaved() { // See saveAllState() for the explanation of this. We do this for // all platform versions, to keep our behavior more consistent between // them. return mStateSaved || mStopped; }

在这个方法中,会判断当前Fragment的状态,有两个值mStateSaved 或者 mStopped有一个为true,那么就会抛异常。

我说一个场景:当用户退出后台的瞬间,调用commit事务提交,此时mStopped = true,应用就会崩溃。其实这种场景还是比较常见的,在开发中可能很少碰到,但是如果上线后从bugly中可能会看到这种崩溃,但是用户其实是无感知的,所以这种情况下建议使用commitAllowingStateLoss。

所以当Activity状态发生变化的时候,例如退出后台、屏幕旋转等,使用commitAllowingStateLoss不会抛异常。

commitNow和commitNowAllowingStateLoss的区别

这两种提交方式,我们使用的好像比较少,我们看下他俩和前面的有啥区别。 java @Override public void commitNow() { disallowAddToBackStack(); mManager.execSingleAction(this, false); } java @Override public void commitNowAllowingStateLoss() { disallowAddToBackStack(); mManager.execSingleAction(this, true); }

首先从源码中我们看到,这种提交方式是不允许将Fragment加入到回退栈的,在这个方法中,会将mAllowAddToBackStack设置为false。 java @NonNull public FragmentTransaction disallowAddToBackStack() { if (mAddToBackStack) { throw new IllegalStateException( "This transaction is already being added to the back stack"); } mAllowAddToBackStack = false; return this; } 那么这个时候,如果调用addToBackStack方法,因为mAllowAddToBackStack = false,此时就直接抛出异常。 java @NonNull public FragmentTransaction addToBackStack(@Nullable String name) { if (!mAllowAddToBackStack) { throw new IllegalStateException( "This FragmentTransaction is not allowed to be added to the back stack."); } mAddToBackStack = true; mName = name; return this; } 而且调用commitNow方法的时候,如果当前Fragment已经被加入到回退栈了,也会抛出异常。

核心代码 - 2

这是一个区别,接下来我们关注一下commit提交和commitNow提交的另一个区别。如果通过commit提交,那么最终调用这个方法scheduleCommit,我们看到是通过Handler来发送一个消息来异步执行事务的提交; java void scheduleCommit() { synchronized (mPendingActions) { boolean postponeReady = mPostponedTransactions != null && !mPostponedTransactions.isEmpty(); boolean pendingReady = mPendingActions.size() == 1; if (postponeReady || pendingReady) { mHost.getHandler().removeCallbacks(mExecCommit); mHost.getHandler().post(mExecCommit); updateOnBackPressedCallbackEnabled(); } } } 那么commitNow在执行事务提交的时候,我们看下execSingleAction方法,发现是同步完成的,所以两者的另一个区别就是执行事务时,commit是异步操作,而commitNow是同步的。 ```java void execSingleAction(@NonNull OpGenerator action, boolean allowStateLoss) { if (allowStateLoss && (mHost == null || mDestroyed)) { // This FragmentManager isn't attached, so drop the entire transaction. return; } ensureExecReady(allowStateLoss); if (action.generateOps(mTmpRecords, mTmpIsPop)) { mExecutingActions = true; try { removeRedundantOperationsAndExecute(mTmpRecords, mTmpIsPop); } finally { cleanupExec(); } }

updateOnBackPressedCallbackEnabled();
doPendingDeferredStart();
mFragmentStore.burpActive();

} ```

所以这个会解决什么问题呢?熟悉的Handler的伙伴们应该知道,所有Handler发送的消息都会存在MessageQueue中,Looper通过loop方法从MessageQueue中取出事件并执行,所以当我们通过commit去提交添加一个Fragment的时候,如果还没有执行到这个事件,就通过findFragmentByTag or findFragmentById去查找这个Fragment就会找不到,有没有伙伴们碰到过这个问题?

所以如果我们业务场景中必须要保证要拿到这个Fragment,那么建议使用commitNow这个提交方式,但是需要注意回退栈的问题,我们可以通过反射的方式,将mAllowAddToBackStack设置为true,避免抛出异常。但是这种方式也需要根据场景酌情使用,因为频繁地使用commitNow可能会导致卡顿。

1.3 Fragment状态保存

当我们的应用发生异常,或者Activity的状态发生变化时,我们想保存Fragment的状态,以便后续的展示,那么我们先看下 Activity # onSaveInstanceState方法是怎么实现的。 ```java protected void onSaveInstanceState(@NonNull Bundle outState) { outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());

outState.putInt(LAST_AUTOFILL_ID, mLastAutofillId);
// 1
Parcelable p = mFragments.saveAllState();
if (p != null) {
    outState.putParcelable(FRAGMENTS_TAG, p);
}
if (mAutoFillResetNeeded) {
    outState.putBoolean(AUTOFILL_RESET_NEEDED, true);
    getAutofillManager().onSaveInstanceState(outState);
}
dispatchActivitySaveInstanceState(outState);

} ```

首先,我们看调用了mFragments的saveAllState方法,mFragments还是我们之前看到的FragmentController,调用了还是FragmentManager的saveAllState方法。 ```java Parcelable saveAllState() { // Make sure all pending operations have now been executed to get // our state update-to-date. forcePostponedTransactions(); endAnimatingAwayFragments(); execPendingActions(true);

mStateSaved = true;
mNonConfig.setIsStateSaved(true);

// First collect all active fragments.
ArrayList<FragmentState> active = mFragmentStore.saveActiveFragments();

if (active.isEmpty()) {
    if (isLoggingEnabled(Log.VERBOSE)) Log.v(TAG, "saveAllState: no fragments!");
    return null;
}

// Build list of currently added fragments.
ArrayList<String> added = mFragmentStore.saveAddedFragments();

// Now save back stack.
BackStackState[] backStack = null;
if (mBackStack != null) {
    int size = mBackStack.size();
    if (size > 0) {
        backStack = new BackStackState[size];
        for (int i = 0; i < size; i++) {
            backStack[i] = new BackStackState(mBackStack.get(i));
            if (isLoggingEnabled(Log.VERBOSE)) {
                Log.v(TAG, "saveAllState: adding back stack #" + i
                        + ": " + mBackStack.get(i));
            }
        }
    }
}

FragmentManagerState fms = new FragmentManagerState();
fms.mActive = active;
fms.mAdded = added;
fms.mBackStack = backStack;
fms.mBackStackIndex = mBackStackIndex.get();
if (mPrimaryNav != null) {
    fms.mPrimaryNavActiveWho = mPrimaryNav.mWho;
}
fms.mResultKeys.addAll(mResults.keySet());
fms.mResults.addAll(mResults.values());
fms.mLaunchedFragments = new ArrayList<>(mLaunchedFragments);
return fms;

} ``` 首先拿到当前页面展示的Fragment,并将其封装为FragmentState,返回的是一个列表;然后拿到通过事务添加进来的全部Fragment的UUID集合,最终创建一个FragmentManagerState类,然后将全部的Fragment的状态存储到FragmentManagerState中,最终返回的序列化数据就是FragmentManagerState

那么恢复数据,其实就是一个反序列化的过程,通过拿到FragmentManagerState数据之后,恢复所有Fragment在销毁之前的状态。

这里需要注意一点,当系统恢复Fragment的时候,是采用反射的方式进行Fragment的创建,此时是通过newInstance()的方式完成的,所以Fragment一定要有一个空参构造方法,否则直接抛异常。

1.4 Fragment回退栈管理

如果阅读过Navigation源码,我们会发现,当执行commit之前,都会将Fragment加入回退栈,那么将Fragment添加到回退栈和不添加到回退栈,有什么区别呢?

我们看下面这个场景,当创建Fragment01之后,点击按钮跳转到Fragment02,此时我们看当Fragment02展示之后,Fragment01直接走了销毁的流程,而且已经调用了onDestroy方法。

java 2023-02-11 18:35:56.070 30604-30604/com.lay.learn.asm E/TAG: Fragment01 onAttach 2023-02-11 18:35:56.071 30604-30604/com.lay.learn.asm E/TAG: Fragment01 onCreate 2023-02-11 18:35:56.072 30604-30604/com.lay.learn.asm E/TAG: Fragment01 onCreateView 2023-02-11 18:35:56.085 30604-30604/com.lay.learn.asm E/TAG: Fragment01 onViewCreated 2023-02-11 18:35:56.095 30604-30604/com.lay.learn.asm E/TAG: Fragment01 onResume 2023-02-11 18:36:45.065 30604-30604/com.lay.learn.asm E/TAG: Fragment01 onPause 2023-02-11 18:36:45.068 30604-30604/com.lay.learn.asm E/TAG: Fragment01 onStop 2023-02-11 18:36:45.072 30604-30604/com.lay.learn.asm E/TAG: Fragment02 onAttach 2023-02-11 18:36:45.075 30604-30604/com.lay.learn.asm E/TAG: Fragment02 onCreate 2023-02-11 18:36:45.079 30604-30604/com.lay.learn.asm E/TAG: Fragment02 onCreateView 2023-02-11 18:36:45.118 30604-30604/com.lay.learn.asm E/TAG: Fragment02 onViewCreated 2023-02-11 18:36:45.138 30604-30604/com.lay.learn.asm E/TAG: Fragment01 onDestroyView 2023-02-11 18:36:45.162 30604-30604/com.lay.learn.asm E/TAG: Fragment01 onDestroy 2023-02-11 18:36:45.165 30604-30604/com.lay.learn.asm E/TAG: Fragment02 onResume 当我们将Fragment01加入到回退栈之后,我们发现,Fragment01好像并没有调用onDestory方法,仅仅是将View销毁了 java 2023-02-11 18:41:05.538 30912-30912/com.lay.learn.asm E/TAG: Fragment01 onAttach 2023-02-11 18:41:05.540 30912-30912/com.lay.learn.asm E/TAG: Fragment01 onCreate 2023-02-11 18:41:05.542 30912-30912/com.lay.learn.asm E/TAG: Fragment01 onCreateView 2023-02-11 18:41:05.560 30912-30912/com.lay.learn.asm E/TAG: Fragment01 onViewCreated 2023-02-11 18:41:05.576 30912-30912/com.lay.learn.asm E/TAG: Fragment01 onResume 2023-02-11 18:41:09.784 30912-30912/com.lay.learn.asm E/TAG: Fragment01 onPause 2023-02-11 18:41:09.787 30912-30912/com.lay.learn.asm E/TAG: Fragment01 onStop 2023-02-11 18:41:09.788 30912-30912/com.lay.learn.asm E/TAG: Fragment02 onAttach 2023-02-11 18:41:09.790 30912-30912/com.lay.learn.asm E/TAG: Fragment02 onCreate 2023-02-11 18:41:09.796 30912-30912/com.lay.learn.asm E/TAG: Fragment02 onCreateView 2023-02-11 18:41:09.811 30912-30912/com.lay.learn.asm E/TAG: Fragment02 onViewCreated 2023-02-11 18:41:09.819 30912-30912/com.lay.learn.asm E/TAG: Fragment01 onDestroyView 2023-02-11 18:41:09.837 30912-30912/com.lay.learn.asm E/TAG: Fragment02 onResume

那么此时,我们点击返回按钮,这个时候我们发现Fragment01生命周期是从头重新执行了吗?并没有,而是直接从onCreateView开始,没有执行onCreate。

```java 2023-02-11 19:04:42.886 25027-25027/com.lay.learn.asm E/TAG: Fragment01 onAttach 2023-02-11 19:04:42.887 25027-25027/com.lay.learn.asm E/TAG: Fragment01 onCreate 2023-02-11 19:04:42.888 25027-25027/com.lay.learn.asm E/TAG: Fragment01 onCreateView 2023-02-11 19:04:42.897 25027-25027/com.lay.learn.asm E/TAG: Fragment01 onViewCreated 2023-02-11 19:04:42.908 25027-25027/com.lay.learn.asm E/TAG: Fragment01 onResume 2023-02-11 19:04:45.572 25027-25027/com.lay.learn.asm E/TAG: Fragment01 onPause 2023-02-11 19:04:45.572 25027-25027/com.lay.learn.asm E/TAG: Fragment01 onStop 2023-02-11 19:04:45.573 25027-25027/com.lay.learn.asm E/TAG: Fragment02 onAttach 2023-02-11 19:04:45.574 25027-25027/com.lay.learn.asm E/TAG: Fragment02 onCreate 2023-02-11 19:04:45.574 25027-25027/com.lay.learn.asm E/TAG: Fragment02 onCreateView 2023-02-11 19:04:45.582 25027-25027/com.lay.learn.asm E/TAG: Fragment02 onViewCreated 2023-02-11 19:04:45.585 25027-25027/com.lay.learn.asm E/TAG: Fragment01 onDestroyView 2023-02-11 19:04:45.588 25027-25027/com.lay.learn.asm E/TAG: Fragment02 onResume 2023-02-11 19:04:46.874 25027-25027/com.lay.learn.asm E/TAG: Fragment02 onPause 2023-02-11 19:04:46.875 25027-25027/com.lay.learn.asm E/TAG: Fragment02 onStop 2023-02-11 19:04:46.875 25027-25027/com.lay.learn.asm E/TAG: Fragment01 onCreateView 2023-02-11 19:04:46.881 25027-25027/com.lay.learn.asm E/TAG: Fragment01 onViewCreated 2023-02-11 19:04:46.883 25027-25027/com.lay.learn.asm E/TAG: Fragment02 onDestroyView 2023-02-11 19:04:46.884 25027-25027/com.lay.learn.asm E/TAG: Fragment02 onDestroy 2023-02-11 19:04:46.885 25027-25027/com.lay.learn.asm E/TAG: Fragment01 onResume

```

所以加入回退栈的作用,我给伙伴们总结一下:

(1)通过replace的方式执行页面之间的切换,加入回退栈能够避免数据丢失(onDestory时ViewModel数据会被清除),防止页面被直接销毁;

(2)加入回退栈更符合用户行为逻辑,从哪个页面来返回就返回哪个页面,而且不会重复调用onCreate方法,因此可以放网络请求的逻辑,避免多次接口请求。

我们这里带一下Navigation的回退栈管理 ```kotlin private fun navigate( entry: NavBackStackEntry, navOptions: NavOptions?, navigatorExtras: Navigator.Extras? ) { val initialNavigation = state.backStack.value.isEmpty() val restoreState = ( navOptions != null && !initialNavigation && navOptions.shouldRestoreState() && savedIds.remove(entry.id) ) if (restoreState) { // Restore back stack does all the work to restore the entry fragmentManager.restoreBackStack(entry.id) state.push(entry) return } val ft = createFragmentTransaction(entry, navOptions)

if (!initialNavigation) {
    ft.addToBackStack(entry.id)
}

if (navigatorExtras is Extras) {
    for ((key, value) in navigatorExtras.sharedElements) {
        ft.addSharedElement(key, value)
    }
}
ft.commit()
// The commit succeeded, update our view of the world
state.push(entry)

} ``` 这里有个变量需要注意一下:initialNavigation,它是一个boolean类型,判断当前回退栈是否为空,如果是第一次使用,那么就为空,此时不会给当前页面添加回退栈。这里其实很好了解,如果把起点加入回退栈,那么在返回的时候,起点其实已经没有上级页面了,就不知道要往哪跳,所以系统会生成一个空白页面,这里大家可以使用一下。

当然有些问题还是避免不了,因为如果加入回退栈,那么Fragment的onCreateView可能会被多次执行,会导致页面的状态发生变化,无法保留上次页面跳转时状态,页面会被刷新,因此可以考虑使用hide show的方式来进行页面状态管理。

2 Fragment常见问题解决

这个模块我主要介绍一下我们在日常开发中经常会遇到的问题

2.1 Can not perform this action after onSaveInstanceState

在调用commit方法的时候,因为allowStateloss为false,所以需要检查状态。 java private void checkStateLoss() { if (isStateSaved()) { throw new IllegalStateException( "Can not perform this action after onSaveInstanceState"); } } java public boolean isStateSaved() { // See saveAllState() for the explanation of this. We do this for // all platform versions, to keep our behavior more consistent between // them. return mStateSaved || mStopped; }

如果在当前页面发起网络请求,等到请求结果之前可能会有耗时,然后此时跳转到了下一个页面,我们知道这个页面的生命周期会走到onDestoryView,此时会触发onSaveInstance方法,mStateSaved会设置为true,那么此时如果在拿到结果之后又进行了一次commit,就直接回抛出异常。

所以对于commit提交来说,不建议在子线程中进行;如果确实需要这种操作,那么就建议使用commitAllowingStateLoss。

可能很多伙伴也会好奇,在实际开发中这种问题很难碰到,一翻bugly就会看到好多线上用户报这个问题。这个就是用户场景我们无法cover全覆盖,用户可能用2G、3G网络就会出现网络加载缓慢的问题。

2.2 Fragment的重叠问题

这种问题其实如果了解系统的恢复机制,应该还是很好避免。为什么会出现Fragment重叠的问题呢?首先我们做的时候是在Activity # onCreate方法中进行add操作。 ```kotlin override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_my_fragment_actvity) fl_fg = findViewById(R.id.fl_fg) btn_jump = findViewById(R.id.btn_jump)

btn_jump.setOnClickListener {
    val transient = supportFragmentManager.beginTransaction()
    transient.replace(R.id.fl_fg,Fragment02())
    transient.addToBackStack(null)
    transient.commit()
}
//事务管理
val beginTransaction = supportFragmentManager.beginTransaction()
beginTransaction.add(R.id.fl_fg, Fragment01())
beginTransaction.addToBackStack("Fragment01")

beginTransaction.commit()

} ``` 如果此时屏幕进行旋转,前提是在没有任何配置的情况下,Activity会被销毁重建,此时按照我们在1.3小节中对于Fragment状态保存的了解,此时会将Fragment的状态保存为FragmentManagerState并将其序列化,在Activity重建之后,onCreate中会获取FragmentManagerState并重建Fragment,此时其实系统已经帮我们重建了Fragment,但是我们在onCreate中再次进行了add操作,此时就会造成Fragment叠加。

其实我们了解这个机制之后就很好解决了,首先第一种方案:在onSaveInstanceState方法中,不去保存Fragment的状态,但是这种方案可能会有风险,因为一刀切可能会影响其他的功能。

另一种就是调用add的时机,当savedInstanceState为空的时候,一般就是首次进来的时候,这个时候就可以执行add;但是如果是重建状态下,savedInstanceState不为空,就不需要自行add,使用系统帮我们恢复的那一份就行。 ```kotlin if (savedInstanceState == null) { //事务管理 val beginTransaction = supportFragmentManager.beginTransaction() beginTransaction.add(R.id.fl_fg, Fragment01()) beginTransaction.addToBackStack("Fragment01")

beginTransaction.commit()

} ```

除此之外,之前在使用Navigation的时候,因为官方的那种方式是采用replace的方式会导致View的状态丢失,因此自定义了一个FragmentNavigator,但是落实到项目中的时候,发现一个重叠的问题。 Android进阶宝典 -- JetPack Navigation的高级用法(解决路由跳转新建Fragment页面问题)

```kotlin val ft = fragmentManager.beginTransaction() val currentFragment = fragmentManager.primaryNavigationFragment KLog.d(TAG,"currentFragment $currentFragment") //将当前Fragment隐藏 if (currentFragment != null) { ft.hide(currentFragment) } //获取目的地Fragment val destinationId = destination.id.toString() var nextFragment = fragmentManager.findFragmentByTag(destinationId)

if (nextFragment != null) { ft.show(nextFragment) } else { //说明当前Fragment没有被创建过 nextFragment = fragmentManager.fragmentFactory.instantiate(context.classLoader, className) nextFragment.arguments = args ft.add(containerId, nextFragment, destinationId) } ``` 因为官方提交事务都是commit,所以自定义Navigator的时候,事务提交也是采用的commit;所以从1.2小节中我们对于commit的原理的认知,这是一个异步的过程,看下图:

image.png

首先Fragment1为起点,在加载路由表的时候,先将Fragment add到NavHostFragment中;因为commit是一个异步的过程可能有延迟,此时调用navigate从Fragment1跳转到Fragment2,按照上面代码中的逻辑,首先会调用primaryNavigationFragment获取当前页面实例(Fragment1),此时因为commit延迟导致没有获取到,此时currentFragment = null,隐藏失败!

那么当加载完成Fragment2的时候,Fragment1也加载完成,此时两个页面就发生了重叠;所以这种情况下,就需要考虑使用commitNow做同步处理,但是需要注意使用commitNow就不允许加入回退栈,这里还需要考虑使用反射将标志位取反

目前我在业务场景中做的处理是在Fragment1调用onAttach的时候,此时Fragment已经创建,然后调用navigate进行跳转。

当然还有一种Fragment重叠的问题,就是多次调用add往同一个containerId中添加,此时Fragment就会全部重叠到一起,但是这种情况下,不会影响任意一个Fragment的生命周期,即便是下面的Fragment已经不可见了;而replace则是会将当前容器中Fragment销毁,然后再添加新的Fragment,这就是add和replace的区别。

这一篇文章原理性的东西比较多,但是使用上很少介绍,相信大部分的伙伴们应该都了解使用方式,但是在使用的时候可能都会遇到一些问题,如果熟悉了其中的原理,定位问题也会比较迅速,而且在使用api的时候也会更加谨慎。