0 概述
Navigation框架是Jetpack里面的fragment管理框架,将fragment之间的跳转、动画、栈管理等做了统一的封装,并且跳转的关系可以可视化。使用这个框架可以帮助我们处理一些Fragment跳转常见的问题。
相信之前做过类似单Activity多Fragment的技术选型,应用的都会有切身的体会,坑真的很多,尤其是fragment任务栈的管理,Navigation框架如何完美的处理这些问题的,相信会对我们之后的开发有些帮助与提示。
1 跳转
@SuppressWarnings("deprecation")
public void navigate(@IdRes int resId, @Nullable Bundle args, @Nullable NavOptions navOptions,
@Nullable Navigator.Extras navigatorExtras) {
....
//目标节点的ID
@IdRes int destId = resId;
final NavAction navAction = currentNode.getAction(resId);
Bundle combinedArgs = null;
if (navAction != null) {
...
//如果传入的是actionID,根据ActionID拿到页面的ID
destId = navAction.getDestinationId();
...
}
//处理参数和一些异常
....
//获取NavDestination对象
NavDestination node = findDestination(destId);
....
navigate(node, combinedArgs, navOptions, navigatorExtras);
}
private void navigate(@NonNull NavDestination node, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
....
Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
node.getNavigatorName());
Bundle finalArgs = node.addInDefaultArgs(args);
//Navigator的子类有ActivityNavigator/FragmentNavigator等
//通过子类的Navigator.Name注解来获取对应的子类navigator
NavDestination newDest = navigator.navigate(node, finalArgs,
navOptions, navigatorExtras);
//处理stack
if (newDest != null) {
......
// Add all of the remaining parent NavGraphs that aren't
// already on the back stack
mBackStack.addAll(hierarchy);
// And finally, add the new destination
NavBackStackEntry newBackStackEntry = new NavBackStackEntry(newDest, finalArgs);
mBackStack.add(newBackStackEntry);
}
if (popped || newDest != null) {
dispatchOnDestinationChanged();
}
}
所以Navigation框架不仅可以支持Fragment跳转,Activity也可以用,不过好像没什么必要。
点开用的最多的FragmentNavigator.navigate()方法,不出意外是通过反射拿到Fragment的对象,然后通过FragmentTransaction处理页面和参数、动画。
public NavDestination navigate(@NonNull FragmentNavigator.Destination destination, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable androidx.navigation.Navigator.Extras navigatorExtras) {
//从这里可以看到,如果当前页面在后台的时候调用了navigate,会直接被ignore,算是个bug吧,自己要处理下。
if (this.mFragmentManager.isStateSaved()) {
Log.i("FragmentNavigator", "Ignoring navigate() call: FragmentManager has already saved its state");
return null;
} else {
String className = destination.getClassName();
if (className.charAt(0) == '.') {
className = this.mContext.getPackageName() + className;
}
Fragment frag = this.instantiateFragment(this.mContext, this.mFragmentManager, className, args);
frag.setArguments(args);
FragmentTransaction ft = this.mFragmentManager.beginTransaction();
....
if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {
....
ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim);
}
ft.replace(this.mContainerId, frag);
.....
//添加到FragmentManger中的返回栈里
ft.addToBackStack(generateBackStackName(mBackStack.size() + 1, destId));
ft.setReorderingAllowed(true);
ft.commit();
if (isAdded) {
this.mBackStack.add(destId);
return destination;
} else {
return null;
}
}
}
ActivityNavigator.navigate()通过intent和startActivity处理跳转,这里就不贴代码了
整个的跳转先是在NavController里面解析配置文件的节点信息,处理返回栈,然后选择对应的Navigtor进行页面加载。
2 回退栈处理
NavController提供了PopBackStack方法用于退出Fragment
@SuppressWarnings("WeakerAccess") /* synthetic access */
//如果直接调的poBackStack()没有传参数,destinationId即为currentDestinationId.
boolean popBackStackInternal(@IdRes int destinationId, boolean inclusive) {
if (mBackStack.isEmpty()) {
// Nothing to pop if the back stack is empty
return false;
}
ArrayList<Navigator> popOperations = new ArrayList<>();
Iterator<NavBackStackEntry> iterator = mBackStack.descendingIterator();
boolean foundDestination = false;
while (iterator.hasNext()) {
NavDestination destination = iterator.next().getDestination();
Navigator navigator = mNavigatorProvider.getNavigator(destination.getNavigatorName());
//将需要退栈的ID放入list中
if (inclusive || destination.getId() != destinationId) {
popOperations.add(navigator);
}
//编译到destionationId为止
if (destination.getId() == destinationId) {
foundDestination = true;
break;
}
}
if (!foundDestination) {
....
//处理异常
}
boolean popped = false;
//遍历list进行退栈
for (Navigator navigator : popOperations) {
//navigator.popBackStack()进行实际的退栈操作,一个个退栈
if (navigator.popBackStack()) {
mBackStack.removeLast();
popped = true;
} else {
// The pop did not complete successfully, so stop immediately
break;
}
}
return popped;
}
我们还是专注于FragmentNavigator,看下该类中的popBackStack()方法
@Override
public boolean popBackStack() {
if (mBackStack.isEmpty()) {
return false;
}
if (mFragmentManager.isStateSaved()) {
//如果应用程序在后台,popBack和navigate一样也会被忽略
return false;
}
if (mFragmentManager.getBackStackEntryCount() > 0) {
//通过Fragment的popBackStack完成退栈操作
mFragmentManager.popBackStack(generateBackStackName(mBackStack.size(), mBackStack.peekLast()),FragmentManager.POP_BACK_STACK_INCLUSIVE);
mIsPendingBackStackOperation = true;
} // else, we're on the first Fragment, so there's nothing to pop from FragmentManager
mBackStack.removeLast();
return true;
}
Navigation框架的回退栈也是通过FragmentManger的addToBackStack()和popBackStack()的系统方法去处理的,这种方案可以直接支持物理返回键。
如果导航栏用系统的ActionBar等,可以直接用NavigationUI.setupActionBarWithNavController这些方法。
关于对回退栈的监听,可以看到Navcontroller中的mNavigatorProvider里面有
private final NavigatorProvider mNavigatorProvider = new NavigatorProvider() {
@Nullable
@Override
public Navigator<? extends NavDestination> addNavigator(@NonNull String name,
@NonNull Navigator<? extends NavDestination> navigator) {
Navigator<? extends NavDestination> previousNavigator =
super.addNavigator(name, navigator);
if (previousNavigator != navigator) {
if (previousNavigator != null) {
previousNavigator.removeOnNavigatorBackPressListener(mOnBackPressListener);
}
navigator.addOnNavigatorBackPressListener(mOnBackPressListener);
}
return previousNavigator;
}
};
addOnNavigatorBackPressListener看方法名就很明显了,FragmentNavigator的基类Navigator中调用 onBackPressAdded();方法,该方法只在FragmentNavigator被重写了
@Override
protected void onBackPressAdded() {
mFragmentManager.addOnBackStackChangedListener(mOnBackStackChangedListener);
}
FragmentManager有一个addOnBackStackChangedListener的监听,当BackStack发生变化时会触发,包括添加和移除。
3 总结
整个框架以NavController为核心控制器,负责处理逻辑和调用下面几个部分
- NavDestination相关联的类和子类负责将xml配置文件中的id与具体的Fragment或者Activity相关联起来,屏蔽细节给NavController跳转用。
- Navigator相关子类以及NavigatorProvider,是跳转和栈管理、动画处理等这些具体功能的实现者。
- NavHostFragment继承了fragment作为布局的一部分放入我们的xml中,提供findNavController方法获取对应的Controller实例,作为布局和逻辑的桥梁。
最后
关于Fragment和Activity,Fragment的好处是灵活和资源消耗小,比如一个大的Activity有多个Fragment组合而成,想把一部分页面挪到另一部分,或者想把一个独立页面放到一个Tab上去,Fragment都可以轻松完成。Activity作为系统四大组件之一,对于开发来说功能齐备,安全可靠。
个人比较倾向于多Activity多Fragment的设计模式,兼顾灵活性和可维护性,单Activity多fragment虽然有Navigation这样的官方框架,使用下来感觉坑还是很多,而且个人感觉从用户角度来说Fragment跳转和Activity相比并没有明显的区别,开发效率的影响却比较明显。
网友评论