了解Navigation使用后,思考幾個問題
- NavHostFragmnet作為路由容器,是如何解析nav_graph資源文件,從而生成NavGraph對象?
- 跳轉時,路由是如何被執行的?
- 跳轉的路由目標節點,NavDestination又是如何創建的。
- 分析后是否能總結出Navigation的優點和痛點
- 能否解決痛點,該如何解決,有什么思路?
源碼分析從下面的圖入手:
版本:
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_graph" />
核心類介紹:
NavHostFragment: 所有節點的外部容器
NavController: 外部調用的入口,提供路由,回退等核心操作
NavDestination 節點的封裝類對應nav_graph.xml文件中的 </navigation>, </fragment> </activity>, </dialog>目標節點(即Destination),同時有如四個子類:NavGraph,FragmentNavigator#Destination,ActivityNavigator#Destination,DialogFragmentNavigator#Destination
NavGraph 特殊的Destination,將app:navGraph="@navigation/nav_graph解析封裝成NavGraph對象,里面包含nav_graph.xml中的所有信息。根節點為</navigation>
Navigator 抽象類。NavController中的navigation()會轉到它的子類,包括NavGraphNavigator,ActivityNavigator,FragmentNavigator,DialogFragmentNavigator。他們會重寫Navigator的navigation()方法,實現自己的跳轉邏輯
NavigatorProvider: 是各種Navigator的管理者,想要定義自己的Navigator,就必須想這個類里的map進行注冊
源碼分析
理解上面類的作用,我們從容器開始入手,看NavHostFragment,是如何獲取xml中配置的屬性:
app:navGraph="@navigation/nav_graph"
app:defaultNavHost="true"
android:name="androidx.navigation.fragment.NavHostFragment"
xml中的屬性,通常在AttributeSet attrs
中獲取,但Fragment的構造函數顯然不會有此屬性。但我們在此類中發現下面的函數:
@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);
//1
final int graphId = navHost.getResourceId(
androidx.navigation.R.styleable.NavHost_navGraph, 0);
if (graphId != 0) {
//2
mGraphId = graphId;
}
navHost.recycle();
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NavHostFragment);
//3
final boolean defaultHost = a.getBoolean(R.styleable.NavHostFragment_defaultNavHost, false);
if (defaultHost) {
//4
mDefaultNavHost = true;
}
a.recycle();
}
View(ViewGroup),fragment等在可以在XML中定義的標簽,在繪制結束后,會執行onInflate()方法。通過1.處解析,得到nav_graph資源id,并保存在mGraphId變量;3.處解析獲取 app:defaultNavHost="true"
設置的參數。
接下來看OnCreate()方法:
//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);
if (mGraphId != 0) {//4
// Set from onInflate()
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);
}
}
核心代碼如上,接下里是分步走分析
1. mNavController = new NavHostController(context);
跟蹤:
public NavHostController(@NonNull Context context) {
super(context);
}
NavHostController這個類沒有任何邏輯,什么都沒做,目的就是為了和NavHostFragment在形式上統一,直接去看父類: super(context);
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));
}
mNavigatorProvider.addNavigator(new NavGraphNavigator(mNavigatorProvider))
mNavigatorProvider.addNavigator(new ActivityNavigator(mContext));
剛開始就在Navigator管理器NavigatorProvider這個添加NavGraphNavigator和ActivityNavigator跳轉類。這樣做的理由是,Navigation框架作為路由導航,可以不用Fragment和Dialog,但不能沒有啟動頁和Activity路由跳轉類。換句話說,我們的App可以沒有Fragment和Dialog。但不能沒有Activity。而Navigation框架不允許沒有啟動首頁,所以必須有NavGraphNavigator這個啟動首頁的跳轉路由類。
繼續跟蹤:mNavigatorProvider.addNavigator(new ActivityNavigator(mContext))
NavigatorProvider:
@Nullable
public final Navigator<? extends NavDestination> addNavigator(
@NonNull Navigator<? extends NavDestination> navigator) {
//name為 activity
String name = getNameForNavigator(navigator.getClass());
//1.下面會分析這里,記得回頭看
return addNavigator(name, navigator);
}
@NonNull
static String getNameForNavigator(@NonNull Class<? extends Navigator> navigatorClass) {
String name = sAnnotationNames.get(navigatorClass);
if (name == null) {
// annotation此時為:activity/fragment/dialog,navigation
//2. 下面會分析這里,記住這個位置
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());
}
//3. 下面會分析這里,記得回頭看
sAnnotationNames.put(navigatorClass, name);
}
return name;
}
Navigator.Name annotation = navigatorClass.getAnnotation(Navigator.Name.class);
Navigator.Name 是個注解類,他會用在所有Navigator所有子類的類頭,用來標記 子類是什么類型的Navigator,如下:
Activity:
@Navigator.Name("activity")
public class ActivityNavigator extends Navigator<ActivityNavigator.Destination>
Dialog:
@Navigator.Name("dialog")
public final class DialogFragmentNavigator extends Navigator<DialogFragmentNavigator.Destination>
Navigation:
@Navigator.Name("navigation")
public class NavGraphNavigator extends Navigator<NavGraph
Fragment:
@Navigator.Name("fragment")
public class FragmentNavigator extends Navigator<FragmentNavigator.Destination>
恰好對應nav_graph.xml中4中Destination標簽,側面驗證他們都具有各自的navigation()跳轉邏輯。
繼續回到 getNameForNavigator()
方法。
2. annotation此時為:activity/fragment/dialog,navigation
3. sAnnotationNames.put(navigatorClass, name);
存放的格式為put(ActivityNavigator.class,activity)
1. 回到上面的1.位置:
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);
}
mNavigators為HashMap<String, Navigator<? extends NavDestination>> mNavigators
此類中最核心的管理類,添加進去存放的格式為put(activity,ActivityNavigator.class)
同時 返回Navigator對應的NavDestination 很重要
以上第一部分完成。總結如下:
-
NavHostController
這個類沒啥實際作用,就是為了和NavHostFragment形式上同樣,真正的實現都在父類NavController中 - 想要自定義自己的Navigator,必須繼承Navigator,并且在類頭上定義自己的Navigator.Name。
- 自定義的Navigator,必須加入NavigatorProvider#mNavigators這個Map中注冊Navigator.Name的value就是Map的key
深入的有點深,此時回頭看往上看OnCreate()方法2處
2.mNavController.setOnBackPressedDispatcher(requireActivity().getOnBackPressedDispatcher());
此方法是實現fragment回退的關鍵,requireActivity().getOnBackPressedDispatcher()
這個是Activity返回鍵監聽的分發器OnBackPressedDispatcher
。
NavController:
void setOnBackPressedDispatcher(@NonNull OnBackPressedDispatcher dispatcher) {
if (mLifecycleOwner == null) {
throw new IllegalStateException("You must call setLifecycleOwner() before calling "
+ "setOnBackPressedDispatcher()");
}
// Remove the callback from any previous dispatcher
mOnBackPressedCallback.remove();
// Then add it to the new dispatcher
dispatcher.addCallback(mLifecycleOwner, mOnBackPressedCallback);
...略
}
dispatcher.addCallback(mLifecycleOwner, mOnBackPressedCallback);
將獲得的OnBackPressedDispatcher
對象傳入,并向里面注冊監聽OnBackPressedDispatcher
:
OnBackPressedDispatcher:
private final OnBackPressedCallback mOnBackPressedCallback =
new OnBackPressedCallback(false) {
@Override
public void handleOnBackPressed() {
popBackStack();
}
};
注冊監聽后,當dispatcher分發返回鍵點擊事件時,會回調我們注冊的監聽,從而調用popBackStack();
出棧方法
總結:
- 給我們個提示,如果我們有需求要攔截返回鍵,做我們想做的事情,可以像dispatcher注冊我們自己的監聽回調。
此時回頭看往上看OnCreate()方法3處
3. onCreateNavController(mNavController);
跟蹤:
protected void onCreateNavController(@NonNull NavController navController) {
navController.getNavigatorProvider().addNavigator(
new DialogFragmentNavigator(requireContext(), getChildFragmentManager()));
navController.getNavigatorProvider().addNavigator(createFragmentNavigator());
}
protected Navigator<? extends FragmentNavigator.Destination> createFragmentNavigator() {
return new FragmentNavigator(requireContext(), getChildFragmentManager(),
getContainerId());
}
添加DialogFragmentNavigator和FragmentNavigator跳轉支持,此時4種Navigator,全部添加進NavigationProvider的HashMap中。支持4中標跳轉的能力
總結:
- Navigation路由跳轉的容器必須是NavHostFragment,否則無法支持Dialog和Fragment跳轉能力
- 如果自定義FragmentNavigator和DialogFragmentNavigator類型,傳入
的FragmentManager
是getChildFragmentManager()
此時回頭看往上看OnCreate()方法4處:
4. onCreateNavController(mNavController);
if (mGraphId != 0) {//4
// Set from onInflate()
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);
}
}
無論走哪個分支,必然調用mNavController.setGraph()
方法:
在這里暫停,下面跟隨代碼深入,會越來越深,但思路清晰,暫且在這里設置個錨點1,會說回到錨點1 就是setGraph()這個方法
錨點1
@CallSuper
public void setGraph(@NavigationRes int graphResId, @Nullable Bundle startDestinationArgs) {
setGraph(getNavInflater().inflate(graphResId), startDestinationArgs);
}
getNavInflater():
public NavInflater getNavInflater() {
if (mInflater == null) {
mInflater = new NavInflater(mContext, mNavigatorProvider);
}
return mInflater;
}
創建:mInflater,接著進入mInflater#inflate():
@SuppressLint("ResourceType")
@NonNull
public NavGraph inflate(@NavigationRes int graphResId) {
Resources res = mContext.getResources();
XmlResourceParser parser = res.getXml(graphResId);
final AttributeSet attrs = Xml.asAttributeSet(parser);
try {
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG
&& type != XmlPullParser.END_DOCUMENT) {
// Empty loop
}
if (type != XmlPullParser.START_TAG) {
throw new XmlPullParserException("No start tag found");
}
String rootElement = parser.getName();
// 1
NavDestination destination = inflate(res, parser, attrs, graphResId);
if (!(destination instanceof NavGraph)) {
throw new IllegalArgumentException("Root element <" + rootElement + ">"
+ " did not inflate into a NavGraph");
}
2.
return (NavGraph) destination;
} catch (Exception e) {
throw new RuntimeException("Exception inflating "
+ res.getResourceName(graphResId) + " line "
+ parser.getLineNumber(), e);
} finally {
parser.close();
}
}
這個方法的返回值為NavGraph,這點要記住。也就是1處返回的對象destination,實際是NavGraph,所以在2處強轉返回.跟進1處代碼:
錨點2
private NavDestination inflate(@NonNull Resources res, @NonNull XmlResourceParser parser,
@NonNull AttributeSet attrs, int graphResId)
throws XmlPullParserException, IOException {
//1
Navigator<?> navigator = mNavigatorProvider.getNavigator(parser.getName());
//2
final NavDestination dest = navigator.createDestination();
//3.
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)) {
inflateDeepLink(res, dest, attrs);
} else if (TAG_ACTION.equals(name)) {
inflateAction(res, dest, attrs, parser, graphResId);
} else if (TAG_INCLUDE.equals(name) && dest instanceof NavGraph) {
final TypedArray a = res.obtainAttributes(
attrs, androidx.navigation.R.styleable.NavInclude);
final int id = a.getResourceId(
androidx.navigation.R.styleable.NavInclude_graph, 0);
//4
((NavGraph) dest).addDestination(inflate(id));
a.recycle();
} else if (dest instanceof NavGraph) {
//5
((NavGraph) dest).addDestination(inflate(res, parser, attrs, graphResId));
}
}
return dest;
}
這個方法很重要。首先我們知道NavGraph中是包含nav_graph所有節點的內容。所以進入方法時是<navigation>這個根節點標簽,1.中navigator=NavGraphNavigator 2.navigator.createDestination()就是dest=new NavGraph(this) this=NavGraphNavigator:
跟進:
public NavGraph createDestination() {
return new NavGraph(this);
}
public NavGraph(@NonNull Navigator<? extends NavGraph> navGraphNavigator) {
super(navGraphNavigator);
}
public NavDestination(@NonNull Navigator<? extends NavDestination> navigator) {
this(NavigatorProvider.getNameForNavigator(navigator.getClass()));
}
3,中調用的onInflate()則為NavGraph類中的
NavGraph:
@Override
public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs) {
super.onInflate(context, attrs);
TypedArray a = context.getResources().obtainAttributes(attrs,
R.styleable.NavGraphNavigator);
setStartDestination(
a.getResourceId(R.styleable.NavGraphNavigator_startDestination, 0));
mStartDestIdName = getDisplayName(context, mStartDestId);
a.recycle();
}
public final void setStartDestination(@IdRes int startDestId) {
if (startDestId == getId()) {
throw new IllegalArgumentException("Start destination " + startDestId + " cannot use "
+ "the same id as the graph " + this);
}
mStartDestId = startDestId;
mStartDestIdName = null;
}
setStartDestination()根據startDestination就是我們在nav_graph的根節點設置的app:startDestination="@+id/navigation_home"
參數,賦值mStartDestId和mStartDestIdName,這里我們知道,當<navigation>嵌套時,不能使用相同的app:startDestination="@+id/navigation_home"
ID
繼續回到4
解析完根節點后,會在循環中,進入到4或5?,然后遞歸調用。遞歸中dest分別可能為Navigator的另外三個子類ActivityNavigator,DialogFragmentNavigator,FragmentNavigator
然后分別調用他們的navigator.createDestination()方法。然后分別調用:dest.onInflate(mContext, attrs)
逐一解析:ActivityNavigator#Destination#onInflate( Context context, AttributeSet attrs)
@CallSuper
@Override
public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs) {
super.onInflate(context, attrs);
TypedArray a = context.getResources().obtainAttributes(attrs,
R.styleable.ActivityNavigator);
String targetPackage = a.getString(R.styleable.ActivityNavigator_targetPackage);
if (targetPackage != null) {
targetPackage = targetPackage.replace(NavInflater.APPLICATION_ID_PLACEHOLDER,
context.getPackageName());
}
//1
setTargetPackage(targetPackage);
String className = a.getString(R.styleable.ActivityNavigator_android_name);
if (className != null) {
if (className.charAt(0) == '.') {
//2
className = context.getPackageName() + className;
}
//3
setComponentName(new ComponentName(context, className));
}
//4
setAction(a.getString(R.styleable.ActivityNavigator_action));
String data = a.getString(R.styleable.ActivityNavigator_data);
if (data != null) {
//5
setData(Uri.parse(data));
}
setDataPattern(a.getString(R.styleable.ActivityNavigator_dataPattern));
a.recycle();
}
解析<activity>標簽1.獲取包名 2.得到全類名 3.4.5進入方法內部,都是對Intent進行賦值。我們知道,設置ComponentName,我們就可以進行最基本的跳轉,如下:
public final Destination setComponentName(@Nullable ComponentName name) {
if (mIntent == null) {
mIntent = new Intent();
}
mIntent.setComponent(name);
return this;
}
而且從xml代碼中可以看出,id,name是必要參數,有他們2就可以路由跳轉
<activity
android:id="@+id/navigation_home"
android:name="org.devio.proj.navigationpro.NavigationActivity"
android:label="@string/title_home"
tools:layout="@layout/activity_main" />
結論:onInflate()方法的核心是setComponentName(),也就是說當我們自定義Navigatior時,要配置setComponentName()
逐一解析:FragmentNavigator#Destination#onInflate( Context context, AttributeSet attrs)
public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs) {
super.onInflate(context, attrs);
TypedArray a = context.getResources().obtainAttributes(attrs,
R.styleable.FragmentNavigator);
String className = a.getString(R.styleable.FragmentNavigator_android_name);
if (className != null) {
setClassName(className);
}
a.recycle();
}
這個方法就很簡單, setClassName(className);
保存Fragment的全類名
逐一解析:DialogFragmentNavigator#Destination#onInflate( Context context, AttributeSet attrs)
public void onInflate(@NonNull Context context, @NonNull AttributeSet attrs) {
super.onInflate(context, attrs);
TypedArray a = context.getResources().obtainAttributes(attrs,
R.styleable.DialogFragmentNavigator);
String className = a.getString(R.styleable.DialogFragmentNavigator_android_name);
if (className != null) {
setClassName(className);
}
a.recycle();
}
也是如此
回到錨點2
private static final String TAG_ARGUMENT = "argument";1
private static final String TAG_DEEP_LINK = "deepLink";2
private static final String TAG_ACTION = "action";3
private static final String TAG_INCLUDE = "include";4
final String name = parser.getName();
if (TAG_ARGUMENT.equals(name)) {
inflateArgumentForDestination(res, dest, attrs, graphResId);
} else if (TAG_DEEP_LINK.equals(name)) {
inflateDeepLink(res, dest, attrs);
} else if (TAG_ACTION.equals(name)) {
inflateAction(res, dest, attrs, parser, graphResId);
} else if (TAG_INCLUDE.equals(name) && dest instanceof NavGraph) {
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) {
//
((NavGraph) dest).addDestination(inflate(res, parser, attrs, graphResId));
}
通過對1,2,3,4子標簽的解析,然后存到對應的NavDestination中,。同時在遞歸中把所有NavDestination節點addDestination()到NavGraph中:
NavGraph:
SparseArrayCompat<NavDestination> mNodes = new SparseArrayCompat<>();
public final void addDestination(@NonNull NavDestination node) {
...略
mNodes.put(node.getId(), node);
}
總結: 所有NavDestination都要存入mNodes
回到錨點1
private void onGraphCreated(@Nullable Bundle startDestinationArgs) {
...略
if (mGraph != null && mBackStack.isEmpty()) {
boolean deepLinked = !mDeepLinkHandled && mActivity != null
&& handleDeepLink(mActivity.getIntent());
if (!deepLinked) {
// Navigate to the first destination in the graph
// if we haven't deep linked to a destination
//開始真正的跳轉
navigate(mGraph, startDestinationArgs, null, null);
}
} else {
dispatchOnDestinationChanged();
}
}
navigate(mGraph, startDestinationArgs, null, null);
startDestinationArgs,意味著跳轉路由,啟動第一個app:startDestination="@+id/navigation_home"
配置的節點頁面
navigate()
private void navigate(@NonNull NavDestination node, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
boolean popped = false;
boolean launchSingleTop = false;
if (navOptions != null) {
if (navOptions.getPopUpTo() != -1) {
popped = popBackStackInternal(navOptions.getPopUpTo(),
navOptions.isPopUpToInclusive());
}
}
//3
Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
node.getNavigatorName());
Bundle finalArgs = node.addInDefaultArgs(args);
NavDestination newDest = navigator.navigate(node, finalArgs,
navOptions, navigatorExtras);
if (newDest != null) {
...略
//1
// The mGraph should always be on the back stack after you navigate()
if (mBackStack.isEmpty() || mBackStack.getFirst().getDestination() != mGraph) {
NavBackStackEntry entry = new NavBackStackEntry(mContext, mGraph, finalArgs,
mLifecycleOwner, mViewModel);
mBackStack.addFirst(entry);
}
//2
// And finally, add the new destination with its default args
NavBackStackEntry newBackStackEntry = new NavBackStackEntry(mContext, newDest,
newDest.addInDefaultArgs(finalArgs), mLifecycleOwner, mViewModel);
mBackStack.add(newBackStackEntry);
} else if (navOptions != null && navOptions.shouldLaunchSingleTop()) {
launchSingleTop = true;
NavBackStackEntry singleTopBackStackEntry = mBackStack.peekLast();
if (singleTopBackStackEntry != null) {
singleTopBackStackEntry.replaceArguments(finalArgs);
}
}
updateOnBackPressedCallbackEnabled();
if (popped || newDest != null || launchSingleTop) {
dispatchOnDestinationChanged();
}
}
- mGraph必須在導航后加入回退棧mBackStack,如果回退棧為空,那么mGraph一定是第一個添加的元素
- 把新的目標NavDestination也加入進回退棧
經過這里,導航具有了返回棧的能力。
繼續看3.
根據參數得知,navigator為NavGraphNavigator。
NavDestination newDest = navigator.navigate(node, finalArgs, navOptions, navigatorExtras);
newDest為NavGraph。navigator.navigate,會調用到NavGraphNavigator中:
跟蹤到下面方法:
```
NavGraphNavigator:
@Nullable
@Override
public NavDestination navigate(@NonNull NavGraph destination, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Extras navigatorExtras) {
int startId = destination.getStartDestination();
if (startId == 0) {
throw new IllegalStateException("no start destination defined via"
+ " app:startDestination for "
+ destination.getDisplayName());
}
//startDestination可能是ActivityNavigator DialogFragmentNavigator FragmentNavigator NavGraphNavigator
NavDestination startDestination = destination.findNode(startId, false);
if (startDestination == null) {
final String dest = destination.getStartDestDisplayName();
throw new IllegalArgumentException("navigation destination " + dest
+ " is not a direct child of this NavGraph");
}
Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
startDestination.getNavigatorName());
return navigator.navigate(startDestination, startDestination.addInDefaultArgs(args),
navOptions, navigatorExtras);
}
```
int startId = destination.getStartDestination();
找到節點的startId,如果沒配置。拋出異常。找到id對應的節點,找不到拋出異常。
此時設置的首頁節點可能是<activity><fragment><dialog>avigator.navigate()然后繼續開始導航
總結:
- 如果沒配置startId。拋出異常
- 找不到對應的NavDestination,拋出異常
上面就是啟動首頁第一個頁面的導航路由過程,下面路由分到<activity><fragment><dialog>中
ActivityNavigator#navigate()
public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
if (destination.getIntent() == null) {
throw new IllegalStateException("Destination " + destination.getId()
+ " does not have an Intent set.");
}
//1
Intent intent = new Intent(destination.getIntent());
if (args != null) {
intent.putExtras(args);
String dataPattern = destination.getDataPattern();
if (!TextUtils.isEmpty(dataPattern)) {
// Fill in the data pattern with the args to build a valid URI
StringBuffer data = new StringBuffer();
Pattern fillInPattern = Pattern.compile("\\{(.+?)\\}");
Matcher matcher = fillInPattern.matcher(dataPattern);
while (matcher.find()) {
String argName = matcher.group(1);
if (args.containsKey(argName)) {
matcher.appendReplacement(data, "");
//noinspection ConstantConditions
data.append(Uri.encode(args.get(argName).toString()));
} else {
throw new IllegalArgumentException("Could not find " + argName + " in "
+ args + " to fill data pattern " + dataPattern);
}
}
matcher.appendTail(data);
intent.setData(Uri.parse(data.toString()));
}
}
if (navigatorExtras instanceof Extras) {
Extras extras = (Extras) navigatorExtras;
intent.addFlags(extras.getFlags());
}
//2
if (!(mContext instanceof Activity)) {
// If we're not launching from an Activity context we have to launch in a new task.
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
if (navOptions != null && navOptions.shouldLaunchSingleTop()) {
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
}
if (mHostActivity != null) {
final Intent hostIntent = mHostActivity.getIntent();
if (hostIntent != null) {
final int hostCurrentId = hostIntent.getIntExtra(EXTRA_NAV_CURRENT, 0);
if (hostCurrentId != 0) {
intent.putExtra(EXTRA_NAV_SOURCE, hostCurrentId);
}
}
}
//3
final int destId = destination.getId();
intent.putExtra(EXTRA_NAV_CURRENT, destId);
Resources resources = getContext().getResources();
if (navOptions != null) {
int popEnterAnim = navOptions.getPopEnterAnim();
int popExitAnim = navOptions.getPopExitAnim();
if ((popEnterAnim > 0
&& resources.getResourceTypeName(popEnterAnim).equals("animator"))
|| (popExitAnim > 0
&& resources.getResourceTypeName(popExitAnim).equals("animator"))) {
Log.w(LOG_TAG, "Activity destinations do not support Animator resource. Ignoring "
+ "popEnter resource " + resources.getResourceName(popEnterAnim) + " and "
+ "popExit resource " + resources.getResourceName(popExitAnim) + "when "
+ "launching " + destination);
} else {
// For use in applyPopAnimationsToPendingTransition()
intent.putExtra(EXTRA_POP_ENTER_ANIM, popEnterAnim);
intent.putExtra(EXTRA_POP_EXIT_ANIM, popExitAnim);
}
}
//4
if (navigatorExtras instanceof Extras) {
Extras extras = (Extras) navigatorExtras;
ActivityOptionsCompat activityOptions = extras.getActivityOptions();
if (activityOptions != null) {
ActivityCompat.startActivity(mContext, intent, activityOptions.toBundle());
} else {
mContext.startActivity(intent);
}
} else {
mContext.startActivity(intent);
}
if (navOptions != null && mHostActivity != null) {
int enterAnim = navOptions.getEnterAnim();
int exitAnim = navOptions.getExitAnim();
if ((enterAnim > 0 && resources.getResourceTypeName(enterAnim).equals("animator"))
|| (exitAnim > 0
&& resources.getResourceTypeName(exitAnim).equals("animator"))) {
Log.w(LOG_TAG, "Activity destinations do not support Animator resource. "
+ "Ignoring " + "enter resource " + resources.getResourceName(enterAnim)
+ " and exit resource " + resources.getResourceName(exitAnim) + "when "
+ "launching " + destination);
} else if (enterAnim >= 0 || exitAnim >= 0) {
enterAnim = Math.max(enterAnim, 0);
exitAnim = Math.max(exitAnim, 0);
mHostActivity.overridePendingTransition(enterAnim, exitAnim);
}
}
// You can't pop the back stack from the caller of a new Activity,
// so we don't add this navigator to the controller's back stack
return null;
}
- 創建跳轉Intent
- 如果不是通過mContext啟動(其他進程或應用,例如deeplink)設置FLAG_ACTIVITY_NEW_TASK 設置xml中
app:launchSingleTop="true"
則,FLAG_ACTIVITY_SINGLE_TOP - 設置跳轉動畫
- startActivity(intent)跳轉
FragmentNavigator#navigate()
@Nullable
@Override
public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
if (mFragmentManager.isStateSaved()) {
Log.i(TAG, "Ignoring navigate() call: FragmentManager has already"
+ " saved its state");
return null;
}
String className = destination.getClassName();
if (className.charAt(0) == '.') {
className = mContext.getPackageName() + className;
}
//1
final Fragment frag = instantiateFragment(mContext, mFragmentManager,
className, args);
frag.setArguments(args);
final FragmentTransaction ft = mFragmentManager.beginTransaction();
//2
int enterAnim = navOptions != null ? navOptions.getEnterAnim() : -1;
int exitAnim = navOptions != null ? navOptions.getExitAnim() : -1;
int popEnterAnim = navOptions != null ? navOptions.getPopEnterAnim() : -1;
int popExitAnim = navOptions != null ? navOptions.getPopExitAnim() : -1;
if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {
enterAnim = enterAnim != -1 ? enterAnim : 0;
exitAnim = exitAnim != -1 ? exitAnim : 0;
popEnterAnim = popEnterAnim != -1 ? popEnterAnim : 0;
popExitAnim = popExitAnim != -1 ? popExitAnim : 0;
ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim);
}
//3
ft.replace(mContainerId, frag);
ft.setPrimaryNavigationFragment(frag);
final @IdRes int destId = destination.getId();
final boolean initialNavigation = mBackStack.isEmpty();
// TODO Build first class singleTop behavior for fragments
final boolean isSingleTopReplacement = navOptions != null && !initialNavigation
&& navOptions.shouldLaunchSingleTop()
&& mBackStack.peekLast() == destId;
boolean isAdded;
if (initialNavigation) {
isAdded = true;
} else if (isSingleTopReplacement) {
// Single Top means we only want one instance on the back stack
if (mBackStack.size() > 1) {
// If the Fragment to be replaced is on the FragmentManager's
// back stack, a simple replace() isn't enough so we
// remove it from the back stack and put our replacement
// on the back stack in its place
mFragmentManager.popBackStack(
generateBackStackName(mBackStack.size(), mBackStack.peekLast()),
FragmentManager.POP_BACK_STACK_INCLUSIVE);
ft.addToBackStack(generateBackStackName(mBackStack.size(), destId));
}
isAdded = false;
} else {
ft.addToBackStack(generateBackStackName(mBackStack.size() + 1, destId));
isAdded = true;
}
if (navigatorExtras instanceof Extras) {
Extras extras = (Extras) navigatorExtras;
for (Map.Entry<View, String> sharedElement : extras.getSharedElements().entrySet()) {
ft.addSharedElement(sharedElement.getKey(), sharedElement.getValue());
}
}
ft.setReorderingAllowed(true);
//4
ft.commit();
// The commit succeeded, update our view of the world
if (isAdded) {
mBackStack.add(destId);
return destination;
} else {
return null;
}
}
@Deprecated
@NonNull
public Fragment instantiateFragment(@NonNull Context context,
@NonNull FragmentManager fragmentManager,
@NonNull String className, @SuppressWarnings("unused") @Nullable Bundle args) {
return fragmentManager.getFragmentFactory().instantiate(
context.getClassLoader(), className);
}
- 利用全類名,調用instantiateFragment()反射獲得Fragment實例
- 設置動畫效果
- 創建FragmentTransaction事務,用ft.replace展示
- 提交
ft.replace()會重建View(驗證onDestroyView->onCreateView,并不會走到onDestroy),保留實體。
如果我們想以,shou,hide方式該怎么做?
DialogFragmentNavigator#navigate()
DialogFragmentNavigator
@Nullable
@Override
public NavDestination navigate(@NonNull final Destination destination, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
if (mFragmentManager.isStateSaved()) {
Log.i(TAG, "Ignoring navigate() call: FragmentManager has already"
+ " saved its state");
return null;
}
String className = destination.getClassName();
if (className.charAt(0) == '.') {
className = mContext.getPackageName() + className;
}
final Fragment frag = mFragmentManager.getFragmentFactory().instantiate(
mContext.getClassLoader(), className);
if (!DialogFragment.class.isAssignableFrom(frag.getClass())) {
throw new IllegalArgumentException("Dialog destination " + destination.getClassName()
+ " is not an instance of DialogFragment");
}
final DialogFragment dialogFragment = (DialogFragment) frag;
dialogFragment.setArguments(args);
dialogFragment.getLifecycle().addObserver(mObserver);
dialogFragment.show(mFragmentManager, DIALOG_TAG + mDialogCount++);
return destination;
}
dialogFragment.show()he dismiss()顯示隱藏,
整體流程和源碼分析結束。總結一下幾點:
- Navigator.Name 是個注解類,他會用在所有Navigator所有子類的類頭,用來標記 子類是什么類型的Navigator
- 自定義自己的Navigator,必須繼承Navigator,并且在類頭上定義自己的Navigator.Name
- 自定義的Navigator,必須加入NavigatorProvider#mNavigators這個Map中注冊Navigator.Name的value就是Map的key
-
NavHostController
這個類沒啥實際作用,就是為了和NavHostFragment形式上同樣,真正的實現都在父類NavController中 - 有需求要攔截返回鍵,做我們想做的事情,可以像dispatcher注冊我們自己的監聽回調。
- Navigation路由跳轉的容器必須是NavHostFragment,否則無法支持Dialog和Fragment跳轉能力
- 如果自定義FragmentNavigator和DialogFragmentNavigator類型,傳入
的FragmentManager
是getChildFragmentManager()
- 所有NavDestination都要存入NavGraph#mNodes
- 如果沒配置startId。拋出異常,找不到對應的NavDestination,拋出異常
Navigation 優缺點
優點:
支持Activity,Fragment,Dialog跳轉
safesArgs安全數據傳輸
允許自定義導航行為
支持Deeplink
可視化編輯頁面
回退棧管理
Android組件(如:BottomNavigationView)完美交互,JetPack其他組件聯合使用
缺點:
所有節點定義在nav_graph.xml不方便管理,靈活性較差
Fragment切換時用replace()銷貨視圖,重新綁定數據
下篇將對Navigation進行實戰改造去除店xml文件,利用json文件+注解形式,動態生成路由文件和管理路由配置