Fragment是什么?
在没有Fragment之前,如何一个页面非常复杂,我们通常会把一个界面拆分成多个部分把的xml布局,然后用这个几个部分的xml组合成一个界面。
这样的界面很常见,如果不使用
Fragment,一般会用一个两个父布局,控制这两个父布局的显示和隐藏来显示不同的界面。xml布局如下:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".FirstFragment">
<!--界面1-->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</FrameLayout>
<!--界面2-->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</FrameLayout>
</FrameLayout>
或者使用一个父布局,然后根据需要add()或者remove()子布局来显示不同的界面。
<!--Container-->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--动态的add或remove来显示不同的界面-->
</FrameLayout>
但是这个两种方法都有一个缺点就是,如果有一个界面不显示了,我需要关闭或释放一些资源,我需要自己写类似的Activity的onDestory等方法,并且如果我想显示控制的这几个界面的进入、返回顺序的话,也需要我们自己写一个栈来管理。
Fragment本质上就是使用的这种方法,只不过多了生命周期控制,以及调用栈的管理等功能。
Fragment是如何创建的?
Fragment的使用方法有两种
- 在xml中使用
<fragment>,在解析xml布局的时候会根据<fragment>标签中的内容创建对应的Fragment- 使用
FragmentManager动态添加
第二种方法如下,
val fragmentTransaction = supportFragmentManager.beginTransaction()
//R.id.fragment_container可以是任意的ViewGroup
fragmentTransaction.replace(R.id.fragment_container, FirstFragment())
fragmentTransaction.commit()
Fragment的核心类
FragmentTransaction
BackStackRecord继承了FragmentManager
FragemnetTransacation用于记录添加、删除、隐藏Fragement的操作,通过FragmentManager.beginTransaction会new一个BackStackRecord调用FragmentTransaction.add()、FragmentTransaction.replace()等方法最终都会保存到FragmentTransaction中的一个list。
调用
FragmentTransaction.commit之后会正式执行在List<Op>中的的操作。然后调用到FragmentManager.scheduleCommit中。
FragmentTransaction的主要功能就是记录add,replace等相关操作,真正的添加删除Fragment是由Fragmentmanager完成的。
FragmentManager
FragmentManager管理 Fragment的创建、删除等。调用FragmentTransaction.commit 之后会调用FragmentManager.equeueAction把记录的操作放在一个队列中,因此调用Fragment.comit不会立即执行。
boolean execPendingActions(boolean allowStateLoss) {
ensureExecReady(allowStateLoss);
boolean didSomething = false;
while (generateOpsForPendingActions(mTmpRecords, mTmpIsPop)) {
mExecutingActions = true;
try {
//执行add、remove相关操作
removeRedundantOperationsAndExecute(mTmpRecords, mTmpIsPop);
} finally {
cleanupExec();
}
didSomething = true;
}
updateOnBackPressedCallbackEnabled();
doPendingDeferredStart();
mFragmentStore.burpActive();
return didSomething;
}
//执行FragmentTransaction中记录的操作,已经Fragment的生命周期相关方法
private void executeOpsTogether(@NonNull ArrayList<BackStackRecord> records,
@NonNull ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) {
//...
//把FragmentTransaction中记录的数据传递给FragmentManager
//以及把Fragment添加到FragmentManager中法
executeOps(records, isRecordPop, startIndex, endIndex);
//...
// And only then do we move all other fragments to the current state
//更新Fragment生命周期
moveToState(mCurState, true);
Set<SpecialEffectsController> changedControllers = collectChangedControllers(
records, startIndex, endIndex);
for (SpecialEffectsController controller : changedControllers) {
controller.updateOperationDirection(isPop);
controller.markPostponedState();
//执行动画
controller.executePendingOperations();
}
}
Fragment动画
SpecialEffectsController控制Fragment的动画
Controller for all "special effects" (such as Animation, Animator, framework Transition, and AndroidX Transition) that can be applied to a Fragment as part of the addition or removal of that Fragment from its container. Each SpecialEffectsController is responsible for a single ViewGroup container.
SpecialEffectsController是控制在Fragment添加或移除时的动画效果,一个SpecialEffectsController负责管理一个ViewGroup。
在调用Fragment生命周期相关方法后会执行SpecialEffectsController.executeOperations()执行动画,在DefaultSpecialEffectsController.startAnimations()中会分别执行Animator和Animation
private void startAnimations(@NonNull List<AnimationInfo> animationInfos,
@NonNull List<Operation> awaitingContainerChanges,
boolean startedAnyTransition, @NonNull Map<Operation, Boolean> startedTransitions) {
// First run Animators
boolean startedAnyAnimator = false;
for (final AnimationInfo animationInfo : animationInfos)
//...
// Now run Animations
for (final AnimationInfo animationInfo : animationsToRun)
//...
}
常见问题
- IllegalStateException:Can not perform this action after onSaveInstanceState
Activity在调用了onSaveInstanceState之后,如果在调用commit()就会抛出这个异常。因为在Activity.onSaveInstanceState会调用Fragment.saveAllState保存Fragment的状态信息到Bundle中(active,added等相关信息)。保存之后再调用commit()会导致保存的数据和现在的不一样,导致Activity重建从恢复数据的候,恢复的界面状态是之前的。为避免这种情况Android就直接在commit()方法中抛出异常。
Activity的onSaveInstanceState(bundle)是保存Activity的数据以便在界面重建后,能够保持和销毁之前显示的界面一致。
onRestoreInstanceState(bundle)的参数bundle就是之前保存的bundle,之前保存的数据都在bundle中。
//Activity
protected void onSaveInstanceState(@NonNull Bundle outState) {
outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
//保存Fragment的相关数据
Parcelable p = mFragments.saveAllState();
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
getAutofillClientController().onSaveInstanceState(outState);
dispatchActivitySaveInstanceState(outState);
}
//Fragment
Bundle saveAllStateInternal() {
Bundle bundle = new Bundle();
// First save all active fragments.
ArrayList<String> active = mFragmentStore.saveActiveFragments();
// And grab all FragmentState objects
ArrayList<FragmentState> savedState = mFragmentStore.getAllSavedState();
if (savedState.isEmpty()) {
if (isLoggingEnabled(Log.VERBOSE)) {
Log.v(TAG, "saveAllState: no fragments!");
}
} else {
//保存状态信息
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.mBackStackStateKeys.addAll(mBackStackStates.keySet());
fms.mBackStackStates.addAll(mBackStackStates.values());
fms.mLaunchedFragments = new ArrayList<>(mLaunchedFragments);
bundle.putParcelable(FRAGMENT_MANAGER_STATE_TAG, fms);
for (String resultName : mResults.keySet()) {
bundle.putBundle(RESULT_NAME_PREFIX + resultName, mResults.get(resultName));
}
for (FragmentState state : savedState) {
Bundle fragmentBundle = new Bundle();
fragmentBundle.putParcelable(FRAGMENT_STATE_TAG, state);
bundle.putBundle(FRAGMENT_NAME_PREFIX + state.mWho, fragmentBundle);
}
}
return bundle;
}
如何解决这个异常?
Android提供了commitNowAllowingStateLoss这个方法,不会检查Acivity是否已经保存过了。如果能接受保存的页面State和实际的State不一致,就可以使用这个方法替代。