Android中,真实作为承载页面级别的组件就两个:Activity和Fragment。说起Activity,我想同伴们都非常了解,假如想新建一个页面,那么就创立一个Activity,这个是传统的开发思想。

同伴们能够想一想,当一个项目成规模今后,Activity的量级达到了50+ or 100+,这个时分页面之间的跳转就需求路由来办理,假如想写startActivity写到吐,那么也没问题。那么有没有好的办法能够非常方便地办理页面,并且能够用最少数的Activity,主张同伴们去了解下Navigation,其内部有一套Fragment的办理机制。

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

为啥要出这篇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办法的时分,就会回调这个办法。

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mFragmentLifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE);
    mFragments.dispatchCreate();
}

首要咱们看一下,mFragments是什么,它是一个FragmentController,从字面意思上看,是Fragment的一个控制器

final FragmentController mFragments = FragmentController.createController(new HostCallbacks());

当Activity履行onCreate办法的时分,终究是调用FragmentManager的dispatchCreate办法

public void dispatchStart() {
    mHost.mFragmentManager.dispatchStart();
}
void dispatchCreate() {
    mStateSaved = false;
    mStopped = false;
    mNonConfig.setIsStateSaved(false);
    // 核心代码 - 1 
    dispatchStateChange(Fragment.CREATED);
}

核心代码 – 1

咱们看下Fragment的生命周期是怎么产生变化的,首要在dispatchStateChange办法中,会传入一个int值,

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去哪了,为啥只到了RESUMED

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办法。

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的生命周期状况。

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)比当时要大,例如:

f.mState:CREATED -> 1
newState:VIEW_CREATED -> 2
fragmentStateManager.createView();

此刻就会调用fragmentStateManager的createView办法,终究会调用Fragment的onCreateView办法,进行View的创立。

反之,假如传入的状况(newState)比当时要小,例如:

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进行事务办理,

//事务办理
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是何许人也。

@NonNull
public FragmentTransaction beginTransaction() {
    return new BackStackRecord(this);
}

在调用beginTransaction办法的时分,其实是创立了一个BackStackRecord实例,从字面意思上看是回退栈记载类,用来记载每个Fragment的回退栈的。

那么在调用add、replace、hide、show的时分,其实便是调用BackStackRecord的办法,咱们看下这几个办法的完结。

@NonNull
public FragmentTransaction add(@IdRes int containerViewId, @NonNull Fragment fragment) {
    doAddOp(containerViewId, fragment, null, OP_ADD);
    return this;
}

在调用add的时分,内部调用了doAddOp办法。

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的实例。

void addOp(Op op) {
    mOps.add(op);
    op.mEnterAnim = mEnterAnim;
    op.mExitAnim = mExitAnim;
    op.mPopEnterAnim = mPopEnterAnim;
    op.mPopExitAnim = mPopExitAnim;
}

创立完结之后,存放在mOps数组中。

@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;
}
@NonNull
public FragmentTransaction hide(@NonNull Fragment fragment) {
    addOp(new Op(OP_HIDE, fragment));
    return this;
}
@NonNull
public FragmentTransaction show(@NonNull Fragment fragment) {
    addOp(new Op(OP_SHOW, fragment));
    return this;
}

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

beginTransaction.commit()
beginTransaction.commitAllowingStateLoss()
beginTransaction.commitNow()
beginTransaction.commitNowAllowingStateLoss()

commit履行有以上四种办法,这4种办法有什么差异呢?咱们从源码视点来看一下。

commit和commitAllowingStateLoss的差异

@Override
public int commit() {
    return commitInternal(false);
}

咱们看到,在commit办法履行的时分,其实是调用了commitInternal办法,传入了一个false参数,代表是否答应状况丢掉。

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,答应状况丢掉。

@Override
public int commitAllowingStateLoss() {
    return commitInternal(true);
}

在commitInternal办法中,调用了enqueueAction办法,将此次事务处理参加队列中。

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办法。

private void checkStateLoss() {
    if (isStateSaved()) {
        throw new IllegalStateException(
                "Can not perform this action after onSaveInstanceState");
    }
}
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的差异

这两种提交办法,咱们运用的如同比较少,咱们看下他俩和前面的有啥差异。

@Override
public void commitNow() {
    disallowAddToBackStack();
    mManager.execSingleAction(this, false);
}
@Override
public void commitNowAllowingStateLoss() {
    disallowAddToBackStack();
    mManager.execSingleAction(this, true);
}

首要从源码中咱们看到,这种提交办法是不答应将Fragment参加到回退栈的,在这个办法中,会将mAllowAddToBackStack设置为false。

@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,此刻就直接抛出反常。

@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来发送一个音讯来异步履行事务的提交;

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是同步的。

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办法是怎么完结的。

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办法。

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办法。

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毁掉了

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。

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的回退栈办理

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,所以需求查看状况。

private void checkStateLoss() {
    if (isStateSaved()) {
        throw new IllegalStateException(
                "Can not perform this action after onSaveInstanceState");
    }
}
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操作。

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,运用体系帮咱们康复的那一份就行。

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页面问题)

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的原理的认知,这是一个异步的进程,看下图:

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

首要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的时分也会更加谨慎。