Dynamic Navigator 库扩展了 Jetpack Navigation 组件的功能,使其可使用功能模块中定义的目的地。该库还提供了在导航到这些目的地时无缝安装按需功能模块的功能。
设置
如需支持功能模块,请在应用模块的 build.gradle
文件中使用以下依赖项:
Groovy
dependencies { def nav_version = "2.7.7" api "androidx.navigation:navigation-fragment-ktx:$nav_version" api "androidx.navigation:navigation-ui-ktx:$nav_version" api "androidx.navigation:navigation-dynamic-features-fragment:$nav_version" }
Kotlin
dependencies { val nav_version = "2.7.7" api("androidx.navigation:navigation-fragment-ktx:$nav_version") api("androidx.navigation:navigation-ui-ktx:$nav_version") api("androidx.navigation:navigation-dynamic-features-fragment:$nav_version") }
请注意,其他 Navigation 依赖项应使用 api 配置,以使其可供功能模块使用。
基本用法
若要支持功能模块,请先将应用中的所有 NavHostFragment
实例更改为 androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment
:
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment"
app:navGraph="@navigation/nav_graph"
... />
接下来,为 com.android.dynamic-feature
模块导航图中与 DynamicNavHostFragment
关联的任何 <activity>
、<fragment>
或 <navigation>
目的地添加 app:moduleName
属性。此属性告诉 Dynamic Navigator 库,该目的地属于具有您所指定名称的功能模块。
<fragment
app:moduleName="myDynamicFeature"
android:id="@+id/featureFragment"
android:name="com.google.android.samples.feature.FeatureFragment"
... />
当您导航到其中一个目的地时,Dynamic Navigator 库会先检查是否安装了相应的功能模块。如果相应功能模块已存在,您的应用便会按预期导航到该目的地。如果相应模块不存在,您的应用会在安装该模块时显示中间进度 Fragment 目的地。进度 Fragment 的默认实现会显示带有进度条的基本界面,并负责处理任何安装错误。
![两个加载屏幕,这些屏幕在首次导航到某个功能模块时显示带有进度条的界面](https://developer.android.google.cn/static/images/guide/navigation/dfm-nav-loading.png?authuser=0000&hl=hr)
如需自定义此界面或在您自己的应用屏幕中手动处理安装进度,请参阅自定义进度 Fragment 和监控请求状态部分。
未指定 app:moduleName
的目的地将继续如常工作,不会发生任何变化,其行为与您的应用使用常规 NavHostFragment
时的行为一样。
自定义进度 Fragment
通过将 app:progressDestination
属性设置为您希望用于处理安装进度的目的地的 ID,您可以替换每个导航图的进度 Fragment 实现。您的自定义进度目的地应为衍生自 AbstractProgressFragment
的 Fragment
。您必须替换有关安装进度、错误和其他事件的通知的抽象方法。然后,您便可在自己选择的界面中显示安装进度了。
默认实现的 DefaultProgressFragment
类使用此 API 显示安装进度。
监控请求状态
借助 Dynamic Navigator 库,您可以实现与有关按需分发的用户体验最佳做法中类似的用户体验流程,让用户留在上一个屏幕的上下文中等待安装完成。这意味着您根本不需要显示中间界面或进度 Fragment。
![此屏幕显示了底部的导航栏,其中有一个图标表明某个功能模块正在下载](https://developer.android.google.cn/static/images/guide/navigation/nav-dfm-ui.png?authuser=0000&hl=hr)
在这种情况下,由您负责监控和处理所有安装状态、进度变化和错误等。
如需启动这种非屏蔽导航流程,请将包含 DynamicInstallMonitor
的 DynamicExtras
对象传递给 NavController.navigate()
,如以下示例所示:
Kotlin
val navController = ... val installMonitor = DynamicInstallMonitor() navController.navigate( destinationId, null, null, DynamicExtras(installMonitor) )
Java
NavController navController = ... DynamicInstallMonitor installMonitor = new DynamicInstallMonitor(); navController.navigate( destinationId, null, null, new DynamicExtras(installMonitor); )
调用 navigate()
之后,您应立即检查 installMonitor.isInstallRequired
的值,了解尝试的导航操作是否导致了功能模块的安装。
- 如果值为
false
,表示您已导航到正常的目的地,无需再执行任何其他操作。 如果值为
true
,您应开始观察installMonitor.status
中现在包含的LiveData
对象。此LiveData
对象从 Play Core 库发出SplitInstallSessionState
更新。这些更新包含可用于更新界面的安装进度事件。请记得处理 Play Core 指南中所述的所有相关状态,包括要求用户确认(如有必要)。Kotlin
val navController = ... val installMonitor = DynamicInstallMonitor() navController.navigate( destinationId, null, null, DynamicExtras(installMonitor) ) if (installMonitor.isInstallRequired) { installMonitor.status.observe(this, object : Observer<SplitInstallSessionState> { override fun onChanged(sessionState: SplitInstallSessionState) { when (sessionState.status()) { SplitInstallSessionStatus.INSTALLED -> { // Call navigate again here or after user taps again in the UI: // navController.navigate(destinationId, destinationArgs, null, null) } SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION -> { SplitInstallManager.startConfirmationDialogForResult(...) } // Handle all remaining states: SplitInstallSessionStatus.FAILED -> {} SplitInstallSessionStatus.CANCELED -> {} } if (sessionState.hasTerminalStatus()) { installMonitor.status.removeObserver(this); } } }); }
Java
NavController navController = ... DynamicInstallMonitor installMonitor = new DynamicInstallMonitor(); navController.navigate( destinationId, null, null, new DynamicExtras(installMonitor); ) if (installMonitor.isInstallRequired()) { installMonitor.getStatus().observe(this, new Observer<SplitInstallSessionState>() { @Override public void onChanged(SplitInstallSessionState sessionState) { switch (sessionState.status()) { case SplitInstallSessionStatus.INSTALLED: // Call navigate again here or after user taps again in the UI: // navController.navigate(mDestinationId, mDestinationArgs, null, null); break; case SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION: SplitInstallManager.startConfirmationDialogForResult(...) break; // Handle all remaining states: case SplitInstallSessionStatus.FAILED: break; case SplitInstallSessionStatus.CANCELED: break; } if (sessionState.hasTerminalStatus()) { installMonitor.getStatus().removeObserver(this); } } }); }
安装完成后,LiveData
对象会发出 SplitInstallSessionStatus.INSTALLED
状态。随后,您应再次调用 NavController.navigate()
。由于相应模块现在已安装,因此调用现在会成功,并且应用会按预期导航到目的地。
达到终止状态后(例如安装完毕时或安装失败时),您应移除 LiveData
Observer,以免出现内存泄露。您可以使用 SplitInstallSessionStatus.hasTerminalStatus()
检查状态是否代表终止状态。
如需查看此 Observer 的示例实现,请参阅 AbstractProgressFragment
。
包含的图表
Dynamic Navigator 库支持包含功能模块中定义的图表。如需包含功能模块中定义的图表,请执行以下操作:
使用
<include-dynamic/>
代替<include/>
,如以下示例所示:<include-dynamic android:id="@+id/includedGraph" app:moduleName="includedgraphfeature" app:graphResName="included_feature_nav" app:graphPackage="com.google.android.samples.dynamic_navigator.included_graph_feature" />
在
<include-dynamic ... />
中,您必须指定以下属性:app:graphResName
:导航图资源文件的名称。该名称衍生自图表的文件名。例如,如果图表位于res/navigation/nav_graph.xml
中,资源名称即为nav_graph
。android:id
- 图表的目的地 ID。Dynamic Navigator 库会忽略在所含图表的根元素中找到的任何android:id
值。app:moduleName
:模块的软件包名称。
使用正确的 graphPackage
请务必正确获取 app:graphPackage
,否则,Navigation 组件将无法包含来自功能模块的指定 navGraph
。
动态功能模块的软件包名称是通过以下方式构造的:将模块的名称附加到基本应用模块的 applicationId
。因此,如果基本应用模块的 com.example.dynamicfeatureapp
为 applicationId
,且动态功能模块的名称为 DynamicFeatureModule
,则动态模块的软件包名称将为 com.example.dynamicfeatureapp.DynamicFeatureModule
。此软件包名称区分大小写。
如果您有任何疑问,可以通过检查生成的 AndroidManifest.xml
来确认功能模块的软件包名称。构建项目后,请转到 <DynamicFeatureModule>/build/intermediates/merged_manifest/debug/AndroidManifest.xml
,它应如下所示:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:dist="http://schemas.android.com/apk/distribution" featureSplit="DynamicFeatureModule" package="com.example.dynamicfeatureapp" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30" /> <dist:module dist:instant="false" dist:title="@string/title_dynamicfeaturemodule" > <dist:delivery> <dist:install-time /> </dist:delivery> <dist:fusing dist:include="true" /> </dist:module> <application /> </manifest>
featureSplit
值应与动态功能模块的名称匹配,且软件包将与基本应用模块的 applicationId
匹配。app:graphPackage
是以下各项的组合:com.example.dynamicfeatureapp.DynamicFeatureModule
。
转到 include-dynamic 导航图
只能导航到 include-dynamic
导航图的 startDestination
。动态模块的导航图由动态模块自行负责,基本应用对此不知情。
借助 include-dynamic 机制,基本应用模块可以包含在动态模块中定义的嵌套导航图。此嵌套导航图的行为类似于任何其他嵌套导航图。根导航图(即嵌套图的父级)只能将嵌套导航图本身定义为目的地,而不能将其子级定义为目的地。因此,当 include-dynamicnavigation 图为目的地时,可以使用 startDestination
。
限制
- 动态包含的图表目前不支持深层链接。
- 动态加载的嵌套图表(即,包含
app:moduleName
的<navigation>
元素)目前不支持深层链接。