Dynamic Navigator 库扩展了 Jetpack Navigation 组件的功能,使其可使用功能模块中定义的目的地。该库还提供了在导航到这些目的地时无缝安装按需功能模块的功能。
注意:如果您不熟悉 Play Feature Delivery 功能,请查看功能模块指南和其他资源后再继续。
设置
如需支持功能模块,请在应用模块的 build.gradle 文件中使用以下依赖项:
dependencies {
def nav_version = "2.3.5"
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:
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.dynamicfeatures.fragment.DynamicNavHostFragment"
app:navGraph="@navigation/nav_graph"
... />
接下来,为 com.android.dynamic-feature 模块导航图中与 DynamicNavHostFragment 关联的任何 、 或 目的地添加 app:moduleName 属性。此属性告诉 Dynamic Navigator 库,该目的地属于具有您所指定名称的功能模块。
app:moduleName="myDynamicFeature"
android:id="@+id/featureFragment"
android:name="com.google.android.samples.feature.FeatureFragment"
... />
当您导航到其中一个目的地时,Dynamic Navigator 库会先检查是否安装了相应的功能模块。如果相应功能模块已存在,您的应用便会按预期导航到该目的地。如果相应模块不存在,您的应用会在安装该模块时显示中间进度 Fragment 目的地。进度 Fragment 的默认实现会显示带有进度条的基本界面,并负责处理任何安装错误。
图 1. 在用户首次导航到按需功能时显示进度条的界面。应用在相应模块下载时显示此屏幕。
如需自定义此界面或在您自己的应用屏幕中手动处理安装进度,请参阅自定义进度 Fragment 和监控请求状态部分。
未指定 app:moduleName 的目的地将继续如常工作,不会发生任何变化,其行为与您的应用使用常规 NavHostFragment 时的行为一样。
自定义进度 Fragment
通过将 app:progressDestination 属性设置为您希望用于处理安装进度的目的地的 ID,您可以替换每个导航图的进度 Fragment 实现。您的自定义进度目的地应为衍生自 AbstractProgressFragment 的 Fragment。您必须替换有关安装进度、错误和其他事件的通知的抽象方法。然后,您便可在自己选择的界面中显示安装进度了。
监控请求状态
借助 Dynamic Navigator 库,您可以实现与有关按需分发的用户体验最佳做法中类似的用户体验流程,让用户留在上一个屏幕的上下文中等待安装完成。这意味着您根本不需要显示中间界面或进度 Fragment。
图 2. 在底部导航栏中显示下载进度的屏幕。
在这种情况下,由您负责监控和处理所有安装状态、进度变化和错误等。
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 {
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() {
@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 库支持包含功能模块中定义的图表。如需包含功能模块中定义的图表,请执行以下操作:
使用 代替 ,如以下示例所示:
android:id="@+id/includedGraph"
app:moduleName="includedgraphfeature"
app:graphResName="included_feature_nav"
app:graphPackage="com.google.android.samples.dynamic_navigator.included_graph_feature" />
在 中,您必须指定以下属性:
app:graphResName:导航图资源文件的名称。该名称衍生自图表的文件名。例如,如果图表位于 res/navigation/nav_graph.xml 中,资源名称即为 nav_graph。
android:id - 图表的目的地 ID。Dynamic Navigator 库会忽略在所含图表的根元素中找到的任何 android:id 值。
app:moduleName:功能模块名称。
限制
动态包含的图表目前不支持深层链接。
动态加载的嵌套图表(即,包含 app:moduleName 的 元素)目前不支持深层链接。
Android Studio 支持
如需将 Navigation Editor 与 Dynamic Navigator 库结合使用,您必须使用 Android Studio 4.0 或更高版本。
在较低版本的 Android Studio 中,您应直接使用导航图的 XML。打开包含本主题中介绍的任意标记或属性的导航图可能会导致异常和其他未定义的行为。