了解Navigation使用后,思考幾個(gè)問題
- NavHostFragmnet作為路由容器愉耙,是如何解析nav_graph資源文件贮尉,從而生成NavGraph對(duì)象?
- 跳轉(zhuǎn)時(shí)朴沿,路由是如何被執(zhí)行的猜谚?
- 跳轉(zhuǎn)的路由目標(biāo)節(jié)點(diǎn),NavDestination又是如何創(chuàng)建的赌渣。
- 分析后是否能總結(jié)出Navigation的優(yōu)點(diǎn)和痛點(diǎn)
- 能否解決痛點(diǎn)魏铅,該如何解決,有什么思路锡垄?
源碼分析從下面的圖入手:
版本:
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: 所有節(jié)點(diǎn)的外部容器
NavController: 外部調(diào)用的入口沦零,提供路由,回退等核心操作
NavDestination 節(jié)點(diǎn)的封裝類對(duì)應(yīng)nav_graph.xml文件中的 </navigation>货岭, </fragment> </activity>路操, </dialog>目標(biāo)節(jié)點(diǎn)(即Destination),同時(shí)有如四個(gè)子類:NavGraph千贯,F(xiàn)ragmentNavigator#Destination屯仗,ActivityNavigator#Destination,DialogFragmentNavigator#Destination
NavGraph 特殊的Destination搔谴,將app:navGraph="@navigation/nav_graph解析封裝成NavGraph對(duì)象魁袜,里面包含nav_graph.xml中的所有信息。根節(jié)點(diǎn)為</navigation>
Navigator 抽象類敦第。NavController中的navigation()會(huì)轉(zhuǎn)到它的子類峰弹,包括NavGraphNavigator,ActivityNavigator芜果,F(xiàn)ragmentNavigator鞠呈,DialogFragmentNavigator。他們會(huì)重寫Navigator的navigation()方法右钾,實(shí)現(xiàn)自己的跳轉(zhuǎn)邏輯
NavigatorProvider: 是各種Navigator的管理者蚁吝,想要定義自己的Navigator旱爆,就必須想這個(gè)類里的map進(jìn)行注冊(cè)
源碼分析
理解上面類的作用,我們從容器開始入手窘茁,看NavHostFragment怀伦,是如何獲取xml中配置的屬性:
app:navGraph="@navigation/nav_graph"
app:defaultNavHost="true"
android:name="androidx.navigation.fragment.NavHostFragment"
xml中的屬性,通常在AttributeSet attrs
中獲取山林,但Fragment的構(gòu)造函數(shù)顯然不會(huì)有此屬性房待。但我們?cè)诖祟愔邪l(fā)現(xiàn)下面的函數(shù):
@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中定義的標(biāo)簽捌朴,在繪制結(jié)束后吴攒,會(huì)執(zhí)行onInflate()方法。通過1.處解析砂蔽,得到nav_graph資源id洼怔,并保存在mGraphId變量;3.處解析獲取 app:defaultNavHost="true"
設(shè)置的參數(shù)左驾。
接下來看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這個(gè)類沒有任何邏輯,什么都沒做诡右,目的就是為了和NavHostFragment在形式上統(tǒng)一安岂,直接去看父類: 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這個(gè)添加NavGraphNavigator和ActivityNavigator跳轉(zhuǎn)類。這樣做的理由是帆吻,Navigation框架作為路由導(dǎo)航域那,可以不用Fragment和Dialog,但不能沒有啟動(dòng)頁(yè)和Activity路由跳轉(zhuǎn)類猜煮。換句話說幔亥,我們的App可以沒有Fragment和Dialog胯舷。但不能沒有Activity柑潦。而Navigation框架不允許沒有啟動(dòng)首頁(yè)件炉,所以必須有NavGraphNavigator這個(gè)啟動(dòng)首頁(yè)的跳轉(zhuǎn)路由類。
繼續(xù)跟蹤: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.下面會(huì)分析這里愕撰,記得回頭看
return addNavigator(name, navigator);
}
@NonNull
static String getNameForNavigator(@NonNull Class<? extends Navigator> navigatorClass) {
String name = sAnnotationNames.get(navigatorClass);
if (name == null) {
// annotation此時(shí)為:activity/fragment/dialog,navigation
//2. 下面會(huì)分析這里刹衫,記住這個(gè)位置
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. 下面會(huì)分析這里,記得回頭看
sAnnotationNames.put(navigatorClass, name);
}
return name;
}
Navigator.Name annotation = navigatorClass.getAnnotation(Navigator.Name.class);
Navigator.Name 是個(gè)注解類搞挣,他會(huì)用在所有Navigator所有子類的類頭带迟,用來標(biāo)記 子類是什么類型的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>
恰好對(duì)應(yīng)nav_graph.xml中4中Destination標(biāo)簽囱桨,側(cè)面驗(yàn)證他們都具有各自的navigation()跳轉(zhuǎn)邏輯邮旷。
繼續(xù)回到 getNameForNavigator()
方法。
2. annotation此時(shí)為: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
此類中最核心的管理類蝇摸,添加進(jìn)去存放的格式為put(activity婶肩,ActivityNavigator.class)
同時(shí) 返回Navigator對(duì)應(yīng)的NavDestination 很重要
以上第一部分完成∶蚕Γ總結(jié)如下:
-
NavHostController
這個(gè)類沒啥實(shí)際作用律歼,就是為了和NavHostFragment形式上同樣,真正的實(shí)現(xiàn)都在父類NavController中 - 想要自定義自己的Navigator啡专,必須繼承Navigator险毁,并且在類頭上定義自己的Navigator.Name。
- 自定義的Navigator们童,必須加入NavigatorProvider#mNavigators這個(gè)Map中注冊(cè)Navigator.Name的value就是Map的key
深入的有點(diǎn)深畔况,此時(shí)回頭看往上看OnCreate()方法2處
2.mNavController.setOnBackPressedDispatcher(requireActivity().getOnBackPressedDispatcher());
此方法是實(shí)現(xiàn)fragment回退的關(guān)鍵,requireActivity().getOnBackPressedDispatcher()
這個(gè)是Activity返回鍵監(jiān)聽的分發(fā)器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
對(duì)象傳入,并向里面注冊(cè)監(jiān)聽OnBackPressedDispatcher
:
OnBackPressedDispatcher:
private final OnBackPressedCallback mOnBackPressedCallback =
new OnBackPressedCallback(false) {
@Override
public void handleOnBackPressed() {
popBackStack();
}
};
注冊(cè)監(jiān)聽后吵瞻,當(dāng)dispatcher分發(fā)返回鍵點(diǎn)擊事件時(shí)甘磨,會(huì)回調(diào)我們注冊(cè)的監(jiān)聽济舆,從而調(diào)用popBackStack();
出棧方法
總結(jié):
- 給我們個(gè)提示,如果我們有需求要攔截返回鍵签夭,做我們想做的事情椎瘟,可以像dispatcher注冊(cè)我們自己的監(jiān)聽回調(diào)。
此時(shí)回頭看往上看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跳轉(zhuǎn)支持肺蔚,此時(shí)4種Navigator煌妈,全部添加進(jìn)NavigationProvider的HashMap中。支持4中標(biāo)跳轉(zhuǎn)的能力
總結(jié):
- Navigation路由跳轉(zhuǎn)的容器必須是NavHostFragment,否則無法支持Dialog和Fragment跳轉(zhuǎn)能力
- 如果自定義FragmentNavigator和DialogFragmentNavigator類型宣羊,傳入
的FragmentManager
是getChildFragmentManager()
此時(shí)回頭看往上看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);
}
}
無論走哪個(gè)分支璧诵,必然調(diào)用mNavController.setGraph()
方法:
在這里暫停,下面跟隨代碼深入仇冯,會(huì)越來越深之宿,但思路清晰,暫且在這里設(shè)置個(gè)錨點(diǎn)1苛坚,會(huì)說回到錨點(diǎn)1 就是setGraph()這個(gè)方法
錨點(diǎn)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;
}
創(chuàng)建:mInflater比被,接著進(jìn)入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();
}
}
這個(gè)方法的返回值為NavGraph色难,這點(diǎn)要記住。也就是1處返回的對(duì)象destination等缀,實(shí)際是NavGraph,所以在2處強(qiáng)轉(zhuǎn)返回.跟進(jìn)1處代碼:
錨點(diǎn)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;
}
這個(gè)方法很重要枷莉。首先我們知道NavGraph中是包含nav_graph所有節(jié)點(diǎn)的內(nèi)容。所以進(jìn)入方法時(shí)是<navigation>這個(gè)根節(jié)點(diǎn)標(biāo)簽尺迂,1.中navigator=NavGraphNavigator 2.navigator.createDestination()就是dest=new NavGraph(this) this=NavGraphNavigator:
跟進(jìn):
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笤妙,中調(diào)用的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()根據(jù)startDestination就是我們?cè)趎av_graph的根節(jié)點(diǎn)設(shè)置的app:startDestination="@+id/navigation_home"
參數(shù),賦值mStartDestId和mStartDestIdName噪裕,這里我們知道蹲盘,當(dāng)<navigation>嵌套時(shí),不能使用相同的app:startDestination="@+id/navigation_home"
ID
繼續(xù)回到4
解析完根節(jié)點(diǎn)后薄嫡,會(huì)在循環(huán)中,進(jìn)入到4或5?哑蔫,然后遞歸調(diào)用闸迷。遞歸中dest分別可能為Navigator的另外三個(gè)子類ActivityNavigator,DialogFragmentNavigator今阳,F(xiàn)ragmentNavigator
然后分別調(diào)用他們的navigator.createDestination()方法。然后分別調(diào)用: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>標(biāo)簽1.獲取包名 2.得到全類名 3.4.5進(jìn)入方法內(nèi)部,都是對(duì)Intent進(jìn)行賦值膝舅。我們知道鼻疮,設(shè)置ComponentName,我們就可以進(jìn)行最基本的跳轉(zhuǎn)崭篡,如下:
public final Destination setComponentName(@Nullable ComponentName name) {
if (mIntent == null) {
mIntent = new Intent();
}
mIntent.setComponent(name);
return this;
}
而且從xml代碼中可以看出,id颠毙,name是必要參數(shù),有他們2就可以路由跳轉(zhuǎn)
<activity
android:id="@+id/navigation_home"
android:name="org.devio.proj.navigationpro.NavigationActivity"
android:label="@string/title_home"
tools:layout="@layout/activity_main" />
結(jié)論:onInflate()方法的核心是setComponentName(),也就是說當(dāng)我們自定義Navigatior時(shí)滴某,要配置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();
}
這個(gè)方法就很簡(jiǎn)單, 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();
}
也是如此
回到錨點(diǎn)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));
}
通過對(duì)1,2晤硕,3,4子標(biāo)簽的解析创译,然后存到對(duì)應(yīng)的NavDestination中软族,掖疮。同時(shí)在遞歸中把所有NavDestination節(jié)點(diǎn)addDestination()到NavGraph中:
NavGraph:
SparseArrayCompat<NavDestination> mNodes = new SparseArrayCompat<>();
public final void addDestination(@NonNull NavDestination node) {
...略
mNodes.put(node.getId(), node);
}
總結(jié): 所有NavDestination都要存入mNodes
回到錨點(diǎn)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
//開始真正的跳轉(zhuǎn)
navigate(mGraph, startDestinationArgs, null, null);
}
} else {
dispatchOnDestinationChanged();
}
}
navigate(mGraph, startDestinationArgs, null, null);
startDestinationArgs,意味著跳轉(zhuǎn)路由搁宾,啟動(dòng)第一個(gè)app:startDestination="@+id/navigation_home"
配置的節(jié)點(diǎn)頁(yè)面
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必須在導(dǎo)航后加入回退棧mBackStack,如果回退棧為空翩腐,那么mGraph一定是第一個(gè)添加的元素
- 把新的目標(biāo)NavDestination也加入進(jìn)回退棧
經(jīng)過這里,導(dǎo)航具有了返回棧的能力疙筹。
繼續(xù)看3.
根據(jù)參數(shù)得知而咆,navigator為NavGraphNavigator。
NavDestination newDest = navigator.navigate(node, finalArgs, navOptions, navigatorExtras);
newDest為NavGraph涯捻。navigator.navigate,會(huì)調(diào)用到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();
找到節(jié)點(diǎn)的startId,如果沒配置涛浙。拋出異常疮薇。找到id對(duì)應(yīng)的節(jié)點(diǎn),找不到拋出異常但骨。
此時(shí)設(shè)置的首頁(yè)節(jié)點(diǎn)可能是<activity><fragment><dialog>avigator.navigate()然后繼續(xù)開始導(dǎo)航
總結(jié):
- 如果沒配置startId呀伙。拋出異常
- 找不到對(duì)應(yīng)的NavDestination箫锤,拋出異常
上面就是啟動(dòng)首頁(yè)第一個(gè)頁(yè)面的導(dǎo)航路由過程阳准,下面路由分到<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;
}
- 創(chuàng)建跳轉(zhuǎn)Intent
- 如果不是通過mContext啟動(dòng)(其他進(jìn)程或應(yīng)用,例如deeplink)設(shè)置FLAG_ACTIVITY_NEW_TASK 設(shè)置xml中
app:launchSingleTop="true"
則乍狐,F(xiàn)LAG_ACTIVITY_SINGLE_TOP - 設(shè)置跳轉(zhuǎn)動(dòng)畫
- startActivity(intent)跳轉(zhuǎn)
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);
}
- 利用全類名惜傲,調(diào)用instantiateFragment()反射獲得Fragment實(shí)例
- 設(shè)置動(dòng)畫效果
- 創(chuàng)建FragmentTransaction事務(wù),用ft.replace展示
- 提交
ft.replace()會(huì)重建View(驗(yàn)證onDestroyView->onCreateView,并不會(huì)走到onDestroy),保留實(shí)體。
如果我們想以吻贿,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()顯示隱藏,
整體流程和源碼分析結(jié)束赠橙∑诰荆總結(jié)一下幾點(diǎn):
- Navigator.Name 是個(gè)注解類,他會(huì)用在所有Navigator所有子類的類頭缤苫,用來標(biāo)記 子類是什么類型的Navigator
- 自定義自己的Navigator,必須繼承Navigator帜矾,并且在類頭上定義自己的Navigator.Name
- 自定義的Navigator珍剑,必須加入NavigatorProvider#mNavigators這個(gè)Map中注冊(cè)Navigator.Name的value就是Map的key
-
NavHostController
這個(gè)類沒啥實(shí)際作用招拙,就是為了和NavHostFragment形式上同樣饰序,真正的實(shí)現(xiàn)都在父類NavController中 - 有需求要攔截返回鍵,做我們想做的事情诉稍,可以像dispatcher注冊(cè)我們自己的監(jiān)聽回調(diào)蚤告。
- Navigation路由跳轉(zhuǎn)的容器必須是NavHostFragment,否則無法支持Dialog和Fragment跳轉(zhuǎn)能力
- 如果自定義FragmentNavigator和DialogFragmentNavigator類型,傳入
的FragmentManager
是getChildFragmentManager()
- 所有NavDestination都要存入NavGraph#mNodes
- 如果沒配置startId箫章。拋出異常,找不到對(duì)應(yīng)的NavDestination戳表,拋出異常
Navigation 優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
支持Activity匾旭,F(xiàn)ragment,Dialog跳轉(zhuǎn)
safesArgs安全數(shù)據(jù)傳輸
允許自定義導(dǎo)航行為
支持Deeplink
可視化編輯頁(yè)面
回退棧管理
Android組件(如:BottomNavigationView)完美交互,JetPack其他組件聯(lián)合使用
缺點(diǎn):
所有節(jié)點(diǎn)定義在nav_graph.xml不方便管理居兆,靈活性較差
Fragment切換時(shí)用replace()銷貨視圖泥栖,重新綁定數(shù)據(jù)
下篇將對(duì)Navigation進(jìn)行實(shí)戰(zhàn)改造去除店xml文件,利用json文件+注解形式钞它,動(dòng)態(tài)生成路由文件和管理路由配置