在Android上探索动态特征导航

在这篇文章中,我们将深入研究动态特性导航库,不仅学习如何在应用程序中使用它,还将了解它的组件如何在引擎盖下工作。

导航到动态功能目标

在开始使用库之前,我们需要继续并将依赖项添加到应用程序中。重要的是要注意到这个库仍然在alpha中,所以如果你有机会使用它,那么这对开发人员来说是一个很好的时机去提供反馈。

implementation "androidx.navigation:navigation-dynamic-features-
    fragment:2.3.0-alpha03"

如果我们以前使用的是导航组件库,那么我们的主机片段应该是这样的:

<androidx.fragment.app.FragmentContainerView
    android:id="@+id/nav_host_fragment"
    android:name="androidx.navigation.fragment.NavHostFragment"
    app:defaultNavHost="true"
    app:navGraph="@navigation/main_nav" />

对于动态导航器,我们需要为新的DynamicNavHostFragment切换此选项。这个导航主机类允许我们使用在动态特性模块中定义的目的地来处理导航。

<androidx.fragment.app.FragmentContainerView
    android:id="@+id/nav_host_fragment"
    android:name="androidx.navigation.dynamicfeatures
        .fragment.DynamicNavHostFragment"
    app:defaultNavHost="true"
    app:navGraph="@navigation/main_nav" />

现在我们的导航主机正在使用DynamicNavHostFragment类,我们可以继续将我们的第一个导航目的地从功能模块添加到我们的图中。

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/main_nav"
    app:startDestination="@id/mainFragment">

    <fragment
        android:id="@+id/mainFragment"
        android:name="co.joebirch.navigationsample.MainFragment" >

</navigation>

现在我们有了导航图,以及 目的地定义为我们的主片段引用。有了这个声明,我们现在想继续并在我们的图中添加一个目的地——我们的第一个目的地将来自一个动态特性模块。让我们继续把它添加到我们的导航图中。

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/main_nav"
    app:startDestination="@id/mainFragment">

    <fragment
        android:id="@+id/mainFragment"
        android:name="co.joebirch.navigationsample.MainFragment" />

    <fragment
        app:moduleName="feature_one"
        android:id="@+id/featureOneFragment"
        android:name="co.joebirch.feature_one.FeatureOneFragment" />

</navigation>

我们在这里添加了我们的目的地,id为 特征碎片。您会注意到,我们在片段目的地设置了三个不同的属性:

1、模块名–这是导航目的地所在模块的名称。在定位目的地时,库将查看此模块的内部,充当导航图和特定目的地之间的粘合剂。与应用程序/库模块导航相比,这是一项重要的附加功能。
2、身份证件–导航目的地的id
3、名称–用于此导航目的地的片段的名称
有了这些定义,我们的图现在有足够的信息来定位作为目的地的片段-现在我们需要实际配置到它的导航。说到这一点,该方法看起来与我们为应用程序和库模块导航配置导航组件的方式非常相似。

首先,我们将继续向mainFragment添加一个操作,以便我们可以导航到我们的功能模块目的地:

<fragment
    android:id="@+id/mainFragment"
    android:name="co.joebirch.navigationsample.MainFragment" >

        <action
            android:id="@+id/action_mainFragment_to_featureOneFragment"
            app:destination="@id/featureOneFragment" />

</fragment>

根据我们的代码,我们现在可以使用 名称控制器并由引用定义执行此操作:

findNavController().navigate(
    R.id.action_mainFragment_to_featureOneFragment)
包括动态特征图的模块

现在,我们已经使用导航图为动态特性模块添加了导航,但在某些情况下,我们可能需要配置稍微复杂一些的导航。例如,我们可能有一个动态特性模块,它定义了自己的导航图——这需要从我们上面定义的导航图中引用。假设我们在第二个动态特性模块中定义了以下导航图:

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    app:startDestination="@id/secondFeatureFragmentOne">

    <fragment
        android:id="@+id/secondFeatureFragmentOne"
     android:name="co.joebirch.navigationsample.feature_two.FeatureTwoFragmentOne">

        <action
            android:id="@+id/action_secondFeatureFragmentOne_to_secondFeatureFragmentTwo"
            app:destination="@id/secondFeatureFragmentTwo" />
    </fragment>

    <fragment
        android:id="@+id/secondFeatureFragmentTwo"
        android:name="co.joebirch.navigationsample.feature_two.FeatureTwoFragmentTwo" />

</navigation>

在这个动态特性模块中,假设我们让它定义自己的导航图——不管这个图是否比这个复杂,这有助于保持这一点的责任。现在,我们要把它作为我们最初定义的全局导航图的一部分,这样我们就可以导航到这个新图形中定义的片段。如果我们回到主导航图,我们可以添加以下信息:

<include-dynamic
    android:id="@+id/featureNav"
    app:moduleName="secondFeature"
    app:graphResName="second_feature_nav"
    app:graphPackage="co.joebirch.navigationsample.feature_two" />

这个包括标签已经在导航库中可用,允许我们从项目中的标准库模块中添加导航图。动态特性导航器库添加了这个新的包括动态标记,可用于从动态功能模块中添加对导航图的引用。此标记有四个可定义的属性:

1、身份证件–导航图的id
2、模块名–图形所在模块的名称
3、名称–用于图形的资源标识符
4、图形包–图形文件所在的包
用这个包括动态添加到我们的原始标签、主标签图形,我们现在可以对它执行导航操作。让我们把 动作主要片段在我们的图形中的操作,以便我们可以导航到包含在包括动态标签。

<action
    android:id="@+id/action_mainFragment_to_featureNav"
    app:destination="@id/featureNav" />

当我们触发 动作主要片段到功能导航从我们的导航控制器的动作,我们的功能导航图的开始目标将被显示,并且任何以下的导航行为将从该图中获取。

在导航期间处理动态功能安装

虽然使用导航库执行动态功能导航非常有帮助,但可能会出现一个大问题:如果在导航时设备上没有实际安装动态功能,该怎么办?幸运的是,动态特性导航库提供了一些类,可以帮助我们解决这方面的问题。

当对动态功能模块执行导航时,如模块属性,则库将首先检查设备上是否安装了该功能。如果已安装,将执行导航到目的地。否则,在安装动态特性的同时会显示一个进度片段,安装完成后用户将被导航到目的地。

当谈到在安装过程中显示的这个进度片段时,它将为我们处理整个安装过程–这包括发生的任何加载、成功或错误状态。这个进度片段的形式是 DefaultProgressFragment类。虽然这个默认片段为我们处理所有事情,但我们可能希望提供我们自己的定制实现,以便在安装过程中使用。但是,强烈建议使用DefaultProgressFragment,除非您需要为这部分流添加扩展功能或自定义超出默认值的ProgressUI。

当涉及到提供我们自己的进度片段时,它需要扩展 AbstractProgressFragment类,这意味着我们将需要实现以下方法:

取消()–当用户取消安装过程时调用
onFailed(错误代码:Int)–当动态功能的安装失败时调用,错误由提供的错误代码指示
onProgress(状态:Int,bytesDownloaded:Long,bytesTotal:Long)–每当动态功能的安装有进度更新时调用。这里,bytesDownloaded表示到目前为止已下载的字节数,以及bytesTotal,即需要下载的总字节数。最后,状态将是 拆分安装会话状态可用于确定动态功能安装的当前状态的值。
一旦我们有了定制的进度片段,我们就可以使用应用程序:评估进度属性设置为处理安装进度的目标ID。

监视动态功能安装

在某些情况下,我们可能希望为我们的动态特性实现一个非阻塞的安装流程,例如,我们不希望显示某种形式的AbstractProgressFragment,而是希望让用户保持在他们所在的当前上下文中。这种方法可以帮助用户获得更流畅的体验,并消除可能来自进度屏幕的任何阻塞体验。

在AbstractProgressFragment类中,有一些内部构件监视动态特性的安装状态,通过 onProgress()覆盖。这是使用DynamicInstallMonitor类来处理的,它实际上可供我们在这个进度片段之外使用,这意味着我们可以允许用户从导航触发安装,监视进程状态,同时继续当前任务,并在安装完成后对其进行导航(处理过程中的任何其他状态,如错误)。

我们可以从创建此监视器的新引用开始:

val installMonitor = DynamicInstallMonitor()

在开始任何监视之前,我们首先要执行导航。执行此操作时,我们需要传入DynamicExtras类的实例。这个类本质上充当了一个容器,在处理动态特性的导航时传递属性。要构建此类的实例,我们可以使用相应的生成器执行以下操作:

val dynamicExtras = DynamicExtras.Builder()
    .setInstallMonitor(installMonitor)
    .build()

生成器当前允许我们设置两个不同的引用:

安装监视器–用于监视动态功能的当前安装状态的引用
目的地–任何领航员。我们希望通过的额外导航
现在我们有了对DynamicExtras的引用,我们可以继续在导航控制器上执行导航:

findNavController().navigate(
    destinationId,
    null,
    null,
    dynamicExtras
)

一旦触发了这个导航操作,我们需要立即检查动态特性的安装状态。我们先前实例化的install monitor实例允许我们使用它的isInstallRequired字段进行检查,这将返回以下任一值:

不是–这意味着不需要安装,我们可以继续执行导航
是的–需要安装动态特性,这意味着我们需要观察安装状态并在安装完成后执行导航。
此时,如果需要安装,则需要观察安装状态。当安装过程自动进行时,我们会检查此安装状态,以便知道是否要观察动态特性的安装状态。我们的installMonitor引用公开了一个LiveData实例,它允许我们观察安装状态何时发生变化。然后,我们可以使用该状态的值来描述应该在UI中显示什么。最后,一旦状态代表了一个终端状态,我们就需要移除观察者,因为不再需要观察。

installMonitor.status.observe(viewLifecycleOwner, Observer { state ->
   when (state.status()) {
       SplitInstallSessionStatus.INSTALLED -> { }
       SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION -> {
           // Larger feature downloads require user confirmation
           splitInstallManager.startConfirmationDialogForResult(
                state,
                this,
                REQUEST_CODE_INSTALL_CONFIRMATION
           )
       }
       SplitInstallSessionStatus.FAILED -> {}
       SplitInstallSessionStatus.CANCELED -> {}
       ...
   }

   if (state.hasTerminalStatus()) {
       installMonitor.status.removeObservers(viewLifecycleOwner)
   }
})

我们可以在上面看到流在任何时候都可能处于的状态集合,而这取决于您计划在这里显示的UI动态交付用户体验指南将有助于处理上述每种状态。

引擎盖下面

既然我们知道了如何在我们的应用程序中处理动态功能导航,那么我就来看看这些功能是如何工作的。

在上面的部分中,我们讨论了这个DynamicNavHostFragment类–这个新类实际上是姓名用于非动态特征导航。DynamicNavHostFragment执行此操作是为了重写 onCreateNavController()函数,使用其中的新类集合来提供动态导航的功能。

如果我们从顶部开始,我们就有了NavHostFragment类,这已经在当前的导航库中提供了,所以我不想对此做太多的介绍。如果您还不熟悉它,那么这个类被用作导航图中显示内容的容器。如果您在上图中注意到,DynamicNavHostFragment是动态导航库中的一个新类,它充当动态特性的导航主机——这个类扩展了原始的NavHostFragment,因此很多功能都是从这个基类继承的。新的宿主片段类实际上只覆盖基类中的一个方法onCreateNavController方法。此覆盖用于在初始化过程中配置导航逻辑的一些额外部分。

在这个onCreateNavController方法中,我们可以看到配置了一组新的导航处理类。在此onCreateNavController期间,提供的导航控制器的Navigator Provider将填充动态导航所需的其他提供程序。

override fun onCreateNavController(navController: NavController) {
    super.onCreateNavController(navController)

    ...
    val navigatorProvider = navController.navigatorProvider
    navigatorProvider += DynamicActivityNavigator(
        requireActivity(), installManager)

    val fragmentNavigator = DynamicFragmentNavigator(requireContext(),
        childFragmentManager, id, installManager)
    navigatorProvider += fragmentNavigator

    val graphNavigator = DynamicGraphNavigator(
        navigatorProvider,
        installManager
    )
    ...
    navigatorProvider += graphNavigator
    navigatorProvider += DynamicIncludeGraphNavigator(requireContext(),
        navigatorProvider, navController.navInflater, installManager)
}

如果我们看一下默认的导航组件,我们可以看到有一组类是从Navigator抽象类扩展而来的——这个类用于定义在应用程序中导航的机制。从这里扩展的类需要实现定义的方法,以便为定义的组件构建导航图。说到动态特性导航,这些原始类被重用以帮助实现动态导航。事实上,每个组件导航器都从其相应的navigator类扩展,重写navigate()函数来处理动态特性导航的新需求。

例如,我们可以跳转到DynamicActivityNavigator类,通过使用DynamicInstallManager查看它处理导航的方式的不同:

override fun navigate(
   destination: ActivityNavigator.Destination,
   args: Bundle?,
   navOptions: NavOptions?,
   navigatorExtras: Navigator.Extras?
): NavDestination? {
   val extras = navigatorExtras as? DynamicExtras
   if (destination is Destination) {
       val moduleName = destination.moduleName
       if (moduleName != null &&
           installManager.needsInstall(moduleName)) {
           return installManager.performInstall(
               destination, args, extras, moduleName)
       }
   }
   return super.navigate(
       destination,
       args,
       navOptions,
       if (extras != null) extras.destinationExtras else navigatorExtras
   )
}

和我们之前在这篇文章中看到的非常相似,对吧?相同的流程检查功能是否可用,然后根据该状态处理导航。如果没有这个新的navigator类,导航组件将无法处理动态特性的这些不同状态。

在导航组件中,您可能会想起NavDestination的概念,该类用于表示导航图中的单个节点–然后将节点拼凑在一起,以创建表示应用程序导航流的图形。navigation组件中的每个Navigator类都包含它们自己的NavDestination实现。当涉及到动态特性及其导航器时,同样适用。

如果我们再次使用DynamicActivityNavigator作为例子,我们可以看到这个类的目标扩展自初始的ActivityNavigator。目标类。目的地的这个定义被用来存放包含动态特性的模块名,这是初始实现不支持的。当在这个活动中导航时,可以在我们的activity()中使用它。

class Destination : ActivityNavigator.Destination {
    var moduleName: String? = null

    constructor(navigatorProvider: NavigatorProvider) :   
        super(navigatorProvider)constructor(
        activityNavigator: Navigator<out ActivityNavigator.Destination>
    ) : super(activityNavigator)

    override fun onInflate(context: Context, attrs: AttributeSet) {
        super.onInflate(context, attrs)
        context.withStyledAttributes(attrs, 
            R.styleable.DynamicActivityNavigator) {
            moduleName =
                getString(R.styleable.DynamicActivityNavigator_moduleName)
        }
    }
}

然后,这些目的地类的使用方式与导航组件先前使用的方法相同。在这里,动态特性导航是在已有的基础上构建的,以扩展动态导航的功能。

从动态特性导航的源代码中我们可以看到,导航组件的许多现有功能都是基于动态特性添加对导航的支持的。这使得开发人员能够使用我们已经熟悉的导航方法,并不仅在源代码中,而且在我们的项目中促进重用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值