最近在公司项目代码中看到了手动调用 commitAllowingStateLoss 的地方,简单看了下这部分代码发现新版本的 fragment 切换实现的还是很精巧的,有必要对源码进行下详细的分析,在这里记录下吧。
先从简单的 commit 开始
inline fun FragmentManager.commit(
allowStateLoss: Boolean = false,
body: FragmentTransaction.() -> Unit
) {
val transaction = beginTransaction()
transaction.body()
if (allowStateLoss) {
transaction.commitAllowingStateLoss()
} else {
transaction.commit()
}
}
BackStackRecord 的 commitInternal
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;
}
最后会执行到 scheduleCommit
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();
}
}
}
/**
* Only call from main thread!
*/
boolean execPendingActions(boolean allowStateLoss) {
ensureExecReady(allowStateLoss);
boolean didSomething = false;
// 从队列中取出 pendingActions
while (generateOpsForPendingActions(mTmpRecords, mTmpIsPop)) {
mExecutingActions = true;
try {
// 进行优化操作后执行 op
removeRedundantOperationsAndExecute(mTmpRecords, mTmpIsPop);
} finally {
cleanupExec();
}
didSomething = true;
}
updateOnBackPressedCallbackEnabled();
doPendingDeferredStart();
mFragmentStore.burpActive();
return didSomething;
}
fragment 操作解析
private void removeRedundantOperationsAndExecute(@NonNull ArrayList<BackStackRecord> records,
@NonNull ArrayList<Boolean> isRecordPop) {
if (records.isEmpty()) {
return;
}
if (records.size() != isRecordPop.size()) {
throw new IllegalStateException("Internal error with the back stack records");
}
// Force start of any postponed transactions that interact with scheduled transactions:
executePostponedTransaction(records, isRecordPop);
final int numRecords = records.size();
int startIndex = 0;
for (int recordNum = 0; recordNum < numRecords; recordNum++) {
// mReorderingAllowed 代表是否允许优化 fragment 操作,如果不允许的话会先执行之前的操作
// 接下来依次执行连续的 mReorderingAllowed 为 false 的操作
// 优化指的是如果先 add A 再 replace 为 B,会被简化为直接 add B
final boolean canReorder = records.get(recordNum).mReorderingAllowed;
if (!canReorder) {
// execute all previous transactions
if (startIndex != recordNum) {
executeOpsTogether(records, isRecordPop, startIndex, recordNum);
}
// execute all pop operations that don't allow reordering together or
// one add operation
int reorderingEnd = recordNum + 1;
if (isRecordPop.get(recordNum)) {
while (reorderingEnd < numRecords
&& isRecordPop.get(reorderingEnd)
&& !records.get(reorderingEnd).mReorderingAllowed) {
reorderingEnd++;
}
}
executeOpsTogether(records, isRecordPop, recordNum, reorderingEnd);
startIndex = reorderingEnd;
recordNum = reorderingEnd - 1;
}
}
if (startIndex != numRecords) {
executeOpsTogether(records, isRecordPop, startIndex, numRecords);
}
}
private void executeOpsTogether(@NonNull ArrayList<BackStackRecord> records,
@NonNull ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) {
final boolean allowReordering = records.get(startIndex).mReorderingAllowed;
boolean addToBackStack = false;
if (mTmpAddedFragments == null) {
mTmpAddedFragments = new ArrayList<>();
} else {
mTmpAddedFragments.clear();
}
mTmpAddedFragments.addAll(mFragmentStore.getFragments());
Fragment oldPrimaryNav = getPrimaryNavigationFragment();
for (int recordNum = startIndex; recordNum < endIndex; recordNum++) {
final BackStackRecord record = records.get(recordNum);
final boolean isPop = isRecordPop.get(recordNum);
if (!isPop) {
// 如果不是 pop 操作,那么接下来对 mTmpAddedFragments 中的 fragment 进行扩展操作
oldPrimaryNav = record.expandOps(mTmpAddedFragments, oldPrimaryNav);
} else {
oldPrimaryNav = record.trackAddedFragmentsInPop(mTmpAddedFragments, oldPrimaryNav);
}
addToBackStack = addToBackStack || record.mAddToBackStack;
}
mTmpAddedFragments.clear();
if (!allowReordering) {
// 如果不允许优化的话先进行变化动画
FragmentTransition.startTransitions(this, records, isRecordPop, startIndex, endIndex,
false, mFragmentTransitionCallback);
}
// 核心代码,目的是将指令转换为对 FragmentManager 的操作
executeOps(records, isRecordPop, startIndex, endIndex);
int postponeIndex = endIndex;
if (allowReordering) {
ArraySet<Fragment> addedFragments = new ArraySet<>();
addAddedFragments(addedFragments);
postponeIndex = postponePostponableTransactions(records, isRecordPop,
startIndex, endIndex, addedFragments);
makeRemovedFragmentsInvisible(addedFragments);
}
if (postponeIndex != startIndex && allowReordering) {
// need to run something now
FragmentTransition.startTransitions(this, records, isRecordPop, startIndex,
postponeIndex, true, mFragmentTransitionCallback);
moveToState(mCurState, true);
}
for (int recordNum = startIndex; recordNum < endIndex; recordNum++) {
final BackStackRecord record = records.get(recordNum);
final boolean isPop = isRecordPop.get(recordNum);
if (isPop && record.mIndex >= 0) {
record.mIndex = -1;
}
record.runOnCommitRunnables();
}
if (addToBackStack) {
reportBackStackChanged();
}
}
// oldPrimaryNav 是导航所用的 Fragment 这里分析的时候先忽略
Fragment expandOps(ArrayList<Fragment> added, Fragment oldPrimaryNav) {
for (int opNum = 0; opNum < mOps.size(); opNum++) {
final Op op = mOps.get(opNum);
switch (op.mCmd) {
case OP_ADD:
case OP_ATTACH:
added.add(op.mFragment);
break;
case OP_REMOVE:
case OP_DETACH: {
added.remove(op.mFragment);
if (op.mFragment == oldPrimaryNav) {
mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, op.mFragment));
opNum++;
oldPrimaryNav = null;
}
}
break;
case OP_REPLACE: {
final Fragment f = op.mFragment;
final int containerId = f.mContainerId;
boolean alreadyAdded = false;
for (int i = added.size() - 1; i >= 0; i--) {
final Fragment old = added.get(i);
if (old.mContainerId == containerId) {
if (old == f) {
// 先查一下之前是否已经添加过该 Fragment
alreadyAdded = true;
} else {
// This is duplicated from above since we only make
// a single pass for expanding ops. Unset any outgoing primary nav.
if (old == oldPrimaryNav) {
mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, old));
opNum++;
oldPrimaryNav = null;
}
// replace 本质上是 remove 掉旧的然后添加新的
final Op removeOp = new Op(OP_REMOVE, old);
removeOp.mEnterAnim = op.mEnterAnim;
removeOp.mPopEnterAnim = op.mPopEnterAnim;
removeOp.mExitAnim = op.mExitAnim;
removeOp.mPopExitAnim = op.mPopExitAnim;
mOps.add(opNum, removeOp);
added.remove(old);
opNum++;
}
}
}
if (alreadyAdded) {
// 如果之前已经添加过的话,这步操作是多余的直接删除该操作就可以了
mOps.remove(opNum);
opNum--;
} else {
op.mCmd = OP_ADD;
added.add(f);
}
}
break;
case OP_SET_PRIMARY_NAV: {
// It's ok if this is null, that means we will restore to no active
// primary navigation fragment on a pop.
mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, oldPrimaryNav));
opNum++;
// Will be set by the OP_SET_PRIMARY_NAV we inserted before when run
oldPrimaryNav = op.mFragment;
}
break;
}
}
return oldPrimaryNav;
}
void executePopOps(boolean moveToState) {
for (int opNum = mOps.size() - 1; opNum >= 0; opNum--) {
final Op op = mOps.get(opNum);
Fragment f = op.mFragment;
if (f != null) {
f.setNextTransition(FragmentManager.reverseTransit(mTransition));
}
switch (op.mCmd) {
case OP_ADD:
f.setNextAnim(op.mPopExitAnim);
mManager.setExitAnimationOrder(f, true);
mManager.removeFragment(f);
break;
case OP_REMOVE:
f.setNextAnim(op.mPopEnterAnim);
mManager.addFragment(f);
break;
case OP_HIDE:
f.setNextAnim(op.mPopEnterAnim);
mManager.showFragment(f);
break;
case OP_SHOW:
f.setNextAnim(op.mPopExitAnim);
mManager.setExitAnimationOrder(f, true);
mManager.hideFragment(f);
break;
case OP_DETACH:
f.setNextAnim(op.mPopEnterAnim);
mManager.attachFragment(f);
break;
case OP_ATTACH:
f.setNextAnim(op.mPopExitAnim);
mManager.setExitAnimationOrder(f, true);
mManager.detachFragment(f);
break;
case OP_SET_PRIMARY_NAV:
mManager.setPrimaryNavigationFragment(null);
break;
case OP_UNSET_PRIMARY_NAV:
mManager.setPrimaryNavigationFragment(f);
break;
case OP_SET_MAX_LIFECYCLE:
mManager.setMaxLifecycle(f, op.mOldMaxState);
break;
default:
throw new IllegalArgumentException("Unknown cmd: " + op.mCmd);
}
if (!mReorderingAllowed && op.mCmd != OP_REMOVE && f != null) {
mManager.moveFragmentToExpectedState(f);
}
}
if (!mReorderingAllowed && moveToState) {
// 不允许优化的话直接进行状态切换,这里会进行 Fragment 生命周期的回调和 Fragment mView 的添加
mManager.moveToState(mManager.mCurState, true);
}
}
addFragment 和 removeFragment
public void addFragment(Fragment fragment, boolean moveToStateNow) {
if (DEBUG) Log.v(TAG, "add: " + fragment);
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);
}
}
}
public void removeFragment(Fragment fragment) {
if (DEBUG) Log.v(TAG, "remove: " + fragment + " nesting=" + fragment.mBackStackNesting);
final boolean inactive = !fragment.isInBackStack();
if (!fragment.mDetached || inactive) {
if (false) {
// Would be nice to catch a bad remove here, but we need
// time to test this to make sure we aren't crashes cases
// where it is not a problem.
if (!mAdded.contains(fragment)) {
throw new IllegalStateException("Fragment not added: " + fragment);
}
}
synchronized (mAdded) {
mAdded.remove(fragment);
}
if (fragment.mHasMenu && fragment.mMenuVisible) {
mNeedMenuInvalidate = true;
}
fragment.mAdded = false;
fragment.mRemoving = true;
}
}
fragmet 状态切换
void moveToState(Fragment f, int newState, int transit, int transitionStyle,
boolean keepActive) {
if (DEBUG && false) Log.v(TAG, "moveToState: " + f
+ " oldState=" + f.mState + " newState=" + newState
+ " mRemoving=" + f.mRemoving + " Callers=" + Debug.getCallers(5));
// Fragments that are not currently added will sit in the onCreate() state.
if ((!f.mAdded || f.mDetached) && newState > Fragment.CREATED) {
newState = Fragment.CREATED;
}
if (f.mRemoving && newState > f.mState) {
if (f.mState == Fragment.INITIALIZING && f.isInBackStack()) {
// Allow the fragment to be created so that it can be saved later.
newState = Fragment.CREATED;
} else {
// While removing a fragment, we can't change it to a higher state.
newState = f.mState;
}
}
// Defer start if requested; don't allow it to move to STARTED or higher
// if it's not already started.
if (f.mDeferStart && f.mState < Fragment.STARTED && newState > Fragment.STOPPED) {
newState = Fragment.STOPPED;
}
if (f.mState <= newState) {
// For fragments that are created from a layout, when restoring from
// state we don't want to allow them to be created until they are
// being reloaded from the layout.
if (f.mFromLayout && !f.mInLayout) {
return;
}
if (f.getAnimatingAway() != null) {
// The fragment is currently being animated... but! Now we
// want to move our state back up. Give up on waiting for the
// animation, move to whatever the final state should be once
// the animation is done, and then we can proceed from there.
f.setAnimatingAway(null);
moveToState(f, f.getStateAfterAnimating(), 0, 0, true);
}
switch (f.mState) {
case Fragment.INITIALIZING:
if (newState > Fragment.INITIALIZING) {
if (DEBUG) Log.v(TAG, "moveto CREATED: " + f);
if (f.mSavedFragmentState != null) {
f.mSavedViewState = f.mSavedFragmentState.getSparseParcelableArray(
FragmentManagerImpl.VIEW_STATE_TAG);
f.mTarget = getFragment(f.mSavedFragmentState,
FragmentManagerImpl.TARGET_STATE_TAG);
if (f.mTarget != null) {
f.mTargetRequestCode = f.mSavedFragmentState.getInt(
FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG, 0);
}
f.mUserVisibleHint = f.mSavedFragmentState.getBoolean(
FragmentManagerImpl.USER_VISIBLE_HINT_TAG, true);
if (!f.mUserVisibleHint) {
f.mDeferStart = true;
if (newState > Fragment.STOPPED) {
newState = Fragment.STOPPED;
}
}
}
f.mHost = mHost;
f.mParentFragment = mParent;
f.mFragmentManager = mParent != null
? mParent.mChildFragmentManager : mHost.getFragmentManagerImpl();
// If we have a target fragment, push it along to at least CREATED
// so that this one can rely on it as an initialized dependency.
if (f.mTarget != null) {
if (mActive.get(f.mTarget.mIndex) != f.mTarget) {
throw new IllegalStateException("Fragment " + f
+ " declared target fragment " + f.mTarget
+ " that does not belong to this FragmentManager!");
}
if (f.mTarget.mState < Fragment.CREATED) {
moveToState(f.mTarget, Fragment.CREATED, 0, 0, true);
}
}
dispatchOnFragmentPreAttached(f, mHost.getContext(), false);
f.mCalled = false;
f.onAttach(mHost.getContext());
if (!f.mCalled) {
throw new SuperNotCalledException("Fragment " + f
+ " did not call through to super.onAttach()");
}
if (f.mParentFragment == null) {
mHost.onAttachFragment(f);
} else {
f.mParentFragment.onAttachFragment(f);
}
dispatchOnFragmentAttached(f, mHost.getContext(), false);
if (!f.mIsCreated) {
dispatchOnFragmentPreCreated(f, f.mSavedFragmentState, false);
f.performCreate(f.mSavedFragmentState);
dispatchOnFragmentCreated(f, f.mSavedFragmentState, false);
} else {
f.restoreChildFragmentState(f.mSavedFragmentState, true);
f.mState = Fragment.CREATED;
}
f.mRetaining = false;
}
// fall through
case Fragment.CREATED:
// This is outside the if statement below on purpose; we want this to run
// even if we do a moveToState from CREATED => *, CREATED => CREATED, and
// * => CREATED as part of the case fallthrough above.
ensureInflatedFragmentView(f);
if (newState > Fragment.CREATED) {
if (DEBUG) Log.v(TAG, "moveto ACTIVITY_CREATED: " + f);
if (!f.mFromLayout) {
ViewGroup container = null;
if (f.mContainerId != 0) {
if (f.mContainerId == View.NO_ID) {
throwException(new IllegalArgumentException(
"Cannot create fragment "
+ f
+ " for a container view with no id"));
}
container = mContainer.onFindViewById(f.mContainerId);
if (container == null && !f.mRestored) {
String resName;
try {
resName = f.getResources().getResourceName(f.mContainerId);
} catch (NotFoundException e) {
resName = "unknown";
}
throwException(new IllegalArgumentException(
"No view found for id 0x"
+ Integer.toHexString(f.mContainerId) + " ("
+ resName
+ ") for fragment " + f));
}
}
f.mContainer = container;
f.mView = f.performCreateView(f.performGetLayoutInflater(
f.mSavedFragmentState), container, f.mSavedFragmentState);
if (f.mView != null) {
f.mView.setSaveFromParentEnabled(false);
if (container != null) {
container.addView(f.mView);
}
if (f.mHidden) {
f.mView.setVisibility(View.GONE);
}
f.onViewCreated(f.mView, f.mSavedFragmentState);
dispatchOnFragmentViewCreated(f, f.mView, f.mSavedFragmentState,
false);
// Only animate the view if it is visible. This is done after
// dispatchOnFragmentViewCreated in case visibility is changed
f.mIsNewlyAdded = (f.mView.getVisibility() == View.VISIBLE)
&& f.mContainer != null;
}
}
f.performActivityCreated(f.mSavedFragmentState);
dispatchOnFragmentActivityCreated(f, f.mSavedFragmentState, false);
if (f.mView != null) {
f.restoreViewState(f.mSavedFragmentState);
}
f.mSavedFragmentState = null;
}
// fall through
case Fragment.ACTIVITY_CREATED:
if (newState > Fragment.ACTIVITY_CREATED) {
f.mState = Fragment.STOPPED;
}
// fall through
case Fragment.STOPPED:
if (newState > Fragment.STOPPED) {
if (DEBUG) Log.v(TAG, "moveto STARTED: " + f);
f.performStart();
dispatchOnFragmentStarted(f, false);
}
// fall through
case Fragment.STARTED:
if (newState > Fragment.STARTED) {
if (DEBUG) Log.v(TAG, "moveto RESUMED: " + f);
f.performResume();
dispatchOnFragmentResumed(f, false);
// Get rid of this in case we saved it and never needed it.
f.mSavedFragmentState = null;
f.mSavedViewState = null;
}
}
} else if (f.mState > newState) {
switch (f.mState) {
case Fragment.RESUMED:
if (newState < Fragment.RESUMED) {
if (DEBUG) Log.v(TAG, "movefrom RESUMED: " + f);
f.performPause();
dispatchOnFragmentPaused(f, false);
}
// fall through
case Fragment.STARTED:
if (newState < Fragment.STARTED) {
if (DEBUG) Log.v(TAG, "movefrom STARTED: " + f);
f.performStop();
dispatchOnFragmentStopped(f, false);
}
// fall through
case Fragment.STOPPED:
case Fragment.ACTIVITY_CREATED:
if (newState < Fragment.ACTIVITY_CREATED) {
if (DEBUG) Log.v(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) {
saveFragmentViewState(f);
}
}
f.performDestroyView();
dispatchOnFragmentViewDestroyed(f, false);
if (f.mView != null && f.mContainer != null) {
if (getTargetSdk() >= Build.VERSION_CODES.O) {
// Stop any current animations:
f.mView.clearAnimation();
f.mContainer.endViewTransition(f.mView);
}
Animator anim = null;
if (mCurState > Fragment.INITIALIZING && !mDestroyed
&& f.mView.getVisibility() == View.VISIBLE
&& f.mView.getTransitionAlpha() > 0) {
anim = loadAnimator(f, transit, false,
transitionStyle);
}
f.mView.setTransitionAlpha(1f);
if (anim != null) {
final ViewGroup container = f.mContainer;
final View view = f.mView;
final Fragment fragment = f;
container.startViewTransition(view);
f.setAnimatingAway(anim);
f.setStateAfterAnimating(newState);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator anim) {
container.endViewTransition(view);
Animator animator = f.getAnimatingAway();
f.setAnimatingAway(null);
// If the animation finished immediately, the fragment's
// view will still be there. If so, we can just pretend
// there was no animation and skip the moveToState()
if (container.indexOfChild(view) == -1
&& animator != null) {
moveToState(fragment, fragment.getStateAfterAnimating(),
0, 0, false);
}
}
});
anim.setTarget(f.mView);
setHWLayerAnimListenerIfAlpha(f.mView, anim);
anim.start();
}
f.mContainer.removeView(f.mView);
}
f.mContainer = null;
f.mView = null;
f.mInLayout = false;
}
// fall through
case Fragment.CREATED:
if (newState < Fragment.CREATED) {
if (mDestroyed) {
if (f.getAnimatingAway() != null) {
// The fragment's containing activity is
// being destroyed, but this fragment is
// currently animating away. Stop the
// animation right now -- it is not needed,
// and we can't wait any more on destroying
// the fragment.
Animator anim = f.getAnimatingAway();
f.setAnimatingAway(null);
anim.cancel();
}
}
if (f.getAnimatingAway() != null) {
// We are waiting for the fragment's view to finish
// animating away. Just make a note of the state
// the fragment now should move to once the animation
// is done.
f.setStateAfterAnimating(newState);
newState = Fragment.CREATED;
} else {
if (DEBUG) Log.v(TAG, "movefrom CREATED: " + f);
if (!f.mRetaining) {
f.performDestroy();
dispatchOnFragmentDestroyed(f, false);
} else {
f.mState = Fragment.INITIALIZING;
}
f.performDetach();
dispatchOnFragmentDetached(f, false);
if (!keepActive) {
if (!f.mRetaining) {
makeInactive(f);
} else {
f.mHost = null;
f.mParentFragment = null;
f.mFragmentManager = null;
}
}
}
}
}
}
if (f.mState != newState) {
Log.w(TAG, "moveToState: Fragment state for " + f + " not updated inline; "
+ "expected state " + newState + " found " + f.mState);
f.mState = newState;
}
}