本篇文章比较适合对 Navigation 有一定了解的同学,因为由于篇幅的原因,本篇文章只会对 Navigation 的源码以及原来做讲解,下面我们进入正文
NavHostFragment 初始化
在 NavHostFragment 的初始化工作中,具体的工作我们可以根据他的生命周期方法来跟踪,但是在使用的他的时候有2种方式
1: 代码手动创建,使用这种方式 那么就需要使用 NavHostFragment 的crate 方法 来创建,将 GraphId 与 startDestinationArgs 传入 ,不过这种情况比较少见
less
@NonNull
public static NavHostFragment create(@NavigationRes int graphResId,
@Nullable Bundle startDestinationArgs) {
Bundle b = null;
if (graphResId != 0) {
b = new Bundle();
b.putInt(KEY_GRAPH_ID, graphResId);
}
if (startDestinationArgs != null) {
if (b == null) {
b = new Bundle();
}
b.putBundle(KEY_START_DESTINATION_ARGS, startDestinationArgs);
}
final NavHostFragment result = new NavHostFragment();
if (b != null) {
result.setArguments(b);
}
return result;
}
2: 在 xml 中引入
ini
<fragment
android:id="@+id/tsm_first_navigation_host"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/tsm_navigation"
android:name="androidx.navigation.fragment.NavHostFragment"/>
需要注意的是 这里指定的是name 就是我们的目标 Fragment
在xml 中引入就会调用 Fragment 的 onInflate 方法,我们来看看 onInflate 方法都做了哪些工作
less
@CallSuper
@Override
public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs,
@Nullable Bundle savedInstanceState) {
super.onInflate(context, attrs, savedInstanceState);
final TypedArray navHost = context.obtainStyledAttributes(attrs,
androidx.navigation.R.styleable.NavHost);
final int graphId = navHost.getResourceId(
androidx.navigation.R.styleable.NavHost_navGraph, 0);
// 找到我们的 navigation xml 文件
if (graphId != 0) {
mGraphId = graphId;
}
navHost.recycle();
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NavHostFragment);
final boolean defaultHost = a.getBoolean(R.styleable.NavHostFragment_defaultNavHost, false);
if (defaultHost) {
// 生命自己是主导航栏,绑定 NavigationMenu 使用
mDefaultNavHost = true;
}
a.recycle();
}
执行完 onInflate 就进入到了 NavHostFragment 的生命周期方法,第一个进入的方法就是 onAttach 方法
less
@CallSuper
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
// TODO This feature should probably be a first-class feature of the Fragment system,
// but it can stay here until we can add the necessary attr resources to
// the fragment lib.
if (mDefaultNavHost) {
getParentFragmentManager().beginTransaction()
// 升级Fragment 的权限,接管系统返回栈
.setPrimaryNavigationFragment(this)
.commit();
}
}
别看这个方法不起眼,但是这里的 setPrimaryNavigationFragment 这个方法是一个至关重要的方法,这个方法让当前的这个Fragment 升级,并且接管系统返回键的操作,具体的内容我们聊到 Navigation 的返回栈的时候会说
离开了这里就来到了 onCreate 方法,
scss
@CallSuper
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
final Context context = requireContext();
// 重要方法1
mNavController = new NavHostController(context);
mNavController.setLifecycleOwner(this);
// 重要方法2
mNavController.setOnBackPressedDispatcher(requireActivity().getOnBackPressedDispatcher());
// Set the default state - this will be updated whenever
// onPrimaryNavigationFragmentChanged() is called
mNavController.enableOnBackPressed(
mIsPrimaryBeforeOnCreate != null && mIsPrimaryBeforeOnCreate);
mIsPrimaryBeforeOnCreate = null;
mNavController.setViewModelStore(getViewModelStore());
// 重要方法3
onCreateNavController(mNavController);
Bundle navState = null;
if (savedInstanceState != null) {
navState = savedInstanceState.getBundle(KEY_NAV_CONTROLLER_STATE);
if (savedInstanceState.getBoolean(KEY_DEFAULT_NAV_HOST, false)) {
mDefaultNavHost = true;
getParentFragmentManager().beginTransaction()
.setPrimaryNavigationFragment(this)
.commit();
}
mGraphId = savedInstanceState.getInt(KEY_GRAPH_ID);
}
if (navState != null) {
// Navigation controller state overrides arguments
mNavController.restoreState(navState);
}
if (mGraphId != 0) {
// Set from onInflate()
// 重要方法4
mNavController.setGraph(mGraphId);
} else {
// See if it was set by NavHostFragment.create()
final Bundle args = getArguments();
final int graphId = args != null ? args.getInt(KEY_GRAPH_ID) : 0;
final Bundle startDestinationArgs = args != null
? args.getBundle(KEY_START_DESTINATION_ARGS)
: null;
if (graphId != 0) {
mNavController.setGraph(graphId, startDestinationArgs);
}
}
// We purposefully run this last as this will trigger the onCreate() of
// child fragments, which may be relying on having the NavController already
// created and having its state restored by that point.
super.onCreate(savedInstanceState);
}
我们先来说一下重要方法一: 创建 NavHostController ,
ini
public NavController(@NonNull Context context) {
mContext = context;
while (context instanceof ContextWrapper) {
if (context instanceof Activity) {
mActivity = (Activity) context;
break;
}
context = ((ContextWrapper) context).getBaseContext();
}
mNavigatorProvider.addNavigator(new NavGraphNavigator(mNavigatorProvider));
mNavigatorProvider.addNavigator(new ActivityNavigator(mContext));
}
在创建 NavHostController 的创建过程中 向他私有 变量 mNavigatorProvider 中添加了2个 Navigator NavGraphNavigation 与 ActivityNavigator , 具体保存方式下
less
@Navigator.Name("navigation")
public class NavGraphNavigator extends Navigator<NavGraph> {
}
@Navigator.Name("activity")
public class ActivityNavigator extends Navigator<ActivityNavigator.Destination> {
}
public class NavigatorProvider {
// 保存不同种类的 Navigator 的名字
private static final HashMap<Class<?>, String> sAnnotationNames = new HashMap<>();
/// 根据名字保存不同种类的 Navigator 的实例
private final HashMap<String, Navigator<? extends NavDestination>> mNavigators =
new HashMap<>();
// 通过Class 上的 @Navigator.Name 注释来获取这个类型的名字
@NonNull
static String getNameForNavigator(@NonNull Class<? extends Navigator> navigatorClass) {
String name = sAnnotationNames.get(navigatorClass);
if (name == null) {
Navigator.Name annotation = navigatorClass.getAnnotation(Navigator.Name.class);
name = annotation != null ? annotation.value() : null;
if (!validateName(name)) {
throw new IllegalArgumentException("No @Navigator.Name annotation found for "
+ navigatorClass.getSimpleName());
}
sAnnotationNames.put(navigatorClass, name);
}
return name;
}
// 调用保存方法,不同类型的实例只能有一个,所以使用 name 来作为key ,name 是根据类注解来获取的
@Nullable
public final Navigator<? extends NavDestination> addNavigator(
@NonNull Navigator<? extends NavDestination> navigator) {
String name = getNameForNavigator(navigator.getClass());
return addNavigator(name, navigator);
}
//使用HashMap<String ,Navigation> 来保存数据
@CallSuper
@Nullable
public Navigator<? extends NavDestination> addNavigator(@NonNull String name,
@NonNull Navigator<? extends NavDestination> navigator) {
if (!validateName(name)) {
throw new IllegalArgumentException("navigator name cannot be an empty string");
}
return mNavigators.put(name, navigator);
}
}
从上面代码可以看到 NavigatorProvider 添加Navigator 先通过 添加的Navigator 的实例获取Class,在通过注解获取 name,同时将获取的name 与 Class 也使用HashMap 缓存起来(框架都喜欢这么干,空间换时间) ,最后来保存的.
重要方法2 也是返回栈相关的问题,这个需要先将前面的基础搞懂再去分析
重要方法3: onCreateNavController(mNavController); 先来看一下方法的具体实现再做分析
less
@CallSuper
protected void onCreateNavController(@NonNull NavController navController) {
navController.getNavigatorProvider().addNavigator(
new DialogFragmentNavigator(requireContext(), getChildFragmentManager()));
navController.getNavigatorProvider().addNavigator(createFragmentNavigator());
}
这方法看着是不是和我们上面讲到的重要方法1里面的内容极其的相似,简直就是一个内容,到了这里NavigatorProvider 中的 4类 Navigator 我们已经知道了
json
{
"navigation" : NavGraphNavigator
"activity" : ActivityNavigator
"dialog" : DialogFragmentNavigator
"fragment" : FragmentNavigator
}
接下来就到了重要方法4:mNavController.setGraph 这段代码最终会调用到 下面2段代码,
less
@CallSuper
public void setGraph(@NavigationRes int graphResId, @Nullable Bundle startDestinationArgs) {
// 这里引入了一个新的 inflate ,NavInflater
setGraph(getNavInflater().inflate(graphResId), startDestinationArgs);
}
@CallSuper
public void setGraph(@NonNull NavGraph graph, @Nullable Bundle startDestinationArgs) {
// 设置新的Graph ,如果之前的Graph 存在,就需要走回退栈
if (mGraph != null) {
// Pop everything from the old graph off the back stack
popBackStackInternal(mGraph.getId(), true);
}
// 将新的Graph 赋值给当前的Graph ,并且开始寻找 Destination 目标
mGraph = graph;
onGraphCreated(startDestinationArgs);
}
这里新引入了一个新的 inflate , 这个inflate 的内容相对于View 来说还简单一点 ,具体的解析过程大致内容如下
less
@NonNull
private NavDestination inflate(@NonNull Resources res, @NonNull XmlResourceParser parser,
@NonNull AttributeSet attrs, int graphResId)
throws XmlPullParserException, IOException {
//注释1. 解析跟节点 , 如果是解析xml ,那么根据xml 第一个节点的名字 navigation
//得到的就是 NavGraphNavigator
// 在逐层遍历的过程中,这个可以是fragment include中的 navigation activity 等,
Navigator<?> navigator = mNavigatorProvider.getNavigator(parser.getName());
//注释2.xml跟节点进来就是创建一个导航节点 NavGraph
// 如果provider 是 不是navigation ,创建的就是其他的 比如 activity 创建的是Destination
final NavDestination dest = navigator.createDestination();
// 在这里 NavGraph 设置的 setStartDestination 开始节点,父类设置的 name id label
dest.onInflate(mContext, attrs);
final int innerDepth = parser.getDepth() + 1;
int type;
int depth;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& ((depth = parser.getDepth()) >= innerDepth
|| type != XmlPullParser.END_TAG)) {
if (type != XmlPullParser.START_TAG) {
continue;
}
if (depth > innerDepth) {
continue;
}
final String name = parser.getName();
if (TAG_ARGUMENT.equals(name)) {
// 设置参数
inflateArgumentForDestination(res, dest, attrs, graphResId);
} else if (TAG_DEEP_LINK.equals(name)) {
//设置deepLink
inflateDeepLink(res, dest, attrs);
} else if (TAG_ACTION.equals(name)) {
// 设置action
inflateAction(res, dest, attrs, parser, graphResId);
} else if (TAG_INCLUDE.equals(name) && dest instanceof NavGraph) {
// 设置 include ,也就是重新创建一个子节点
final TypedArray a = res.obtainAttributes(
attrs, androidx.navigation.R.styleable.NavInclude);
final int id = a.getResourceId(
androidx.navigation.R.styleable.NavInclude_graph, 0);
((NavGraph) dest).addDestination(inflate(id));
a.recycle();
} else if (dest instanceof NavGraph) {
//注释3. 如果是节点NavGraph,就根据 当前节点继续添加, 在 1 这个地方我们得到的就是 fragment
//以此类推
((NavGraph) dest).addDestination(inflate(res, parser, attrs, graphResId));
}
}
return dest;
}
看了上面的注解相信你一定能搞清楚整个解析过程,但是这里还有一个比较重要的内容,那就是 NavGraph 中的子节点是使用 SparseArrayCompat 来保存的,同时 mStartDestIdName 与子节点列表是平级的关系
arduino
final SparseArrayCompat<NavDestination> mNodes = new SparseArrayCompat<>();
private String mStartDestIdName;
我们就以一个
xml
<navigation>
<fragment></fragment>
<fragment></fragment>
<fragment></fragment>
</navigation>
看完了这里我们再来分析一下 NavigatorProvider 中节点的关系
先根据 navigation 去provider 中找到 NavGraphNavigator ,然后通过 NavGraphNavigator 创建出来一个 NavGraph, 这个NavGraph 中包含一个 mStartDestIdName 和一个 SparseArrayCompat , 在while 中会走3次 注释3,并向 NavGraph 的 mNode 中添加3个子节点,具体的添加方法如下
java
public final void addDestination(@NonNull NavDestination node) {
int id = node.getId();
if (id == 0) {
throw new IllegalArgumentException("Destinations must have an id."
+ " Call setId() or include an android:id in your navigation XML.");
}
if (id == getId()) {
throw new IllegalArgumentException("Destination " + node + " cannot have the same id "
+ "as graph " + this);
}
NavDestination existingDestination = mNodes.get(id);
if (existingDestination == node) {
return;
}
if (node.getParent() != null) {
throw new IllegalStateException("Destination already has a parent set."
+ " Call NavGraph.remove() to remove the previous parent.");
}
if (existingDestination != null) {
existingDestination.setParent(null);
}
node.setParent(this);
mNodes.put(node.getId(), node);
}
在添加过程中先将自己点的 parent 设置为自己,然后在添加到 SparseArrayCompat 当中,整个添加过程就结束了
整个初始化的工作就已经分析的差不多了,剩下的就是设置 startDestion 这个工作了,由于时间和内容的关系,可以将剩下的部分同 navigation 的跳转一起来学习