在 Android 和 Hilt 中限定作用域

将对象 A 的作用域限定到对象 B,指的是对象 B 的整个生命周期内始终持有相同的 A 实例。当涉及到 DI (依赖项注入) 时,限定对象 A 的作用域为一个容器,则意味着该容器在销毁之前始终提供相同的 A 实例。

在 Hilt 中,您可以通过注解将类型的作用域限定在某些容器或组件内。例如,您的应用中有一个处理登录和注销的 UserManager 类型。您可以使用 @Singleton 注解将该类型的作用域限定为 ApplicationComponent (ApplicationComponent 是一个被整个应用的生命周期管理的容器)。被限定作用域的类型在应用组件中沿 组件层次结构 向下传递: 在本案例中,相同的 UserManager 实例将被提供给层次结构内其余的 Hilt 组件。应用中任何依赖于 UserManager 的类型都将获得相同的实例。

注意 : 默认情况下,Hilt 中的绑定都 未限定作用域 。这些绑定不属于任何组件,并且可以在整个项目中被访问。每次被请求都会提供该类型的不同实例。当您将绑定的作用域限定为某个组件时,它会限制您使用该绑定的范围以及该类型可以具有的依赖项。

在 Android 中,您不使用 DI 库也可以通过 Android Framework 来手动限定作用域。让我们看看如何手动限定作用域,以及如何改用 Hilt 来限定作用域。最后,我们将比较使用 Android Framework 手动限定作用域和使用 Hilt 限定作用域的区别。

在 Android 中限定作用域

看了上文的定义,您可能会有这样的异议: 在某个特定类中使用一个类型的实例变量也可以做到限定该变量类型的作用域。没错!不使用 DI 时,您可以执行如下操作:

class ExampleActivity : AppCompatActivity() {

  private val analyticsAdapter = AnalyticsAdapter()
  ...

}

analyticsAdapter 变量的作用域被限定为 MyActivity 的生命周期,这意味着只要 Activity 没有被销毁,该变量就是同一个实例。如果另一个类出于某种原因需要访问这个被限定了作用域的变量,每次访问也会获得相同实例。当新的 MyActivity 实例被创建时 (如系统设置改变),一个新的 AnalyticsAdapter 实例将会被创建。

使用 Hilt,等效代码如下:

@ActivityScoped
class AnalyticsAdapter @Inject constructor() { ... }

@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {

 @Inject lateinit var analyticsAdapter: AnalyticsAdapter

}

每次创建的 MyActivity 都会持有一个 ActivityComponent DI 容器的新实例,在 Activity 被销毁之前,该实例将向 组件层次结构 下的依赖项提供相同的 AnalyticsAdapter 实例。

更改系统设置后,您将获得一个新的 AnalyticsAdapter 和 MainActivity 实例

通过 ViewModel 限定作用域

然而,我们可能希望 AnalyticsAdapter 可以在系统设置更改后留存!或者说,我们希望直到用户离开 Activity 之前,都限定该实例的作用域为 Activity。

为此,您可以使用 组件架构中的 ViewModel,因为它可以在系统设置更改后留存。

不使用依赖项注入时,您可能有如下代码:

class AnalyticsAdapter() { ... }

class ExampleViewModel() : ViewModel() {
  val analyticsAdapter = AnalyticsAdapter()
}

class ExampleActivity : AppCompatActivity() {

  private val viewModel: ExampleViewModel by viewModels()
  private val analyticsAdapter = viewModel.analyticsAdapter

}

通过这种方式,您将 AnalyticsAdapter 的作用域限定为 ViewModel。因为 Activity 具有 ViewModel 的访问权限,所以在该 Activity 中可以始终获得相同的 AnalyticsAdapter 实例。

通过使用 Hilt,您可以通过限定 AnalyticsAdapter 的作用域为 ActivityRetainedComponent 来实现相同的行为,因为 ActivityRetainedComponent 也可以在系统设置更改后留存。

@ActivityRetainedScoped

class AnalyticsAdapter @Inject constructor() { ... }

@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {

@Inject lateinit var analyticsAdapter: AnalyticsAdapter

}

通过使用 ViewModel 或者 Hilt 中的 ActivityRetainedScope 注解,您可以在系统设置更改后获得相同的实例

如果您希望在遵循良好的 DI 实践的同时,保留 ViewModel 用于处理视图逻辑,您可以使用 @ViewModelInject 提供 ViewModel 的依赖项,该注解的详细描述请参见: 文档 | 使用 Hilt 注入 ViewModel 对象。这样一来,AnalyticsAdapter 的作用域就无需被限定为 ActivityRetainedComponent,因为此时它的作用域被手动限定为 ViewModel:

class AnalyticsAdapter @Inject constructor() { ... }

class ExampleViewModel @ViewModelInject constructor(
  private val analyticsAdapter: AnalyticsAdapter
) : ViewModel() { ... }

@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {

  private val viewModel: ExampleViewModel by viewModels()
  private val analyticsAdapter = viewModel.analyticsAdapter

}

我们刚才所看到的内容,可以应用到任何由 Android Framework 生命周期类管理的 Hilt 组件中。点击查看 全部可用作用域。回到我们最初的示例,将作用域限定为 ApplicationComponent,等同于不使用 DI 框架时在 Application 类中持有该实例。

对比 Hilt 及 ViewModel 限定作用域

使用 Hilt 限定作用域,优势为您可在 Hilt 组件层次结构中使用被限定的类型;而对于 ViewModel,则必须通过 ViewModel 手动访问被限定作用域的类型。

使用 ViewModel 限定作用域,优势为您可以在应用中任何 LifecyclerOwner 对象中持有 ViewModel。例如,如果您使用了 Jetpack Navigation 库,则可以将 ViewModel 绑定到 NavGraph 上。

Hilt 提供的作用域数量有限。可能没有符合您特定使用场景的作用域。例如嵌套 Fragment,对于这种情况,您可以退一步使用 ViewModel 限定作用域。

使用 Hilt 注入 ViewModel

如上文所述,您可以使用 @ViewModelInject 向 ViewModel 注入依赖项。其原理是这些绑定关系保存在 ActivityRetainedComponent 中,这也是为什么您只能注入未限定作用域的类型,或者是限定作用域为 ActivityRetainedComponent 以及 ApplicationComponent 的类型。

如果 Activity 或 Fragment 被 @AndroidEntryPoint 注解修饰,就可以通过 getDefaultViewModelProviderFactory() 方法获取 Hilt 生成的 ViewModel 工厂了。由于可以在 ViewModelProvider 中使用这些 ViewModel 工厂,使您获取 ViewModel 的方式变得更加灵活。例如: 将作用域限定为 BackStackEntry 的 ViewModel。

限定作用域会有一些代价,因为提供的对象在持有者被销毁之前将一直保留在内存中。请在应用中慎重地考虑使用限定作用域的对象。如果对象的内部状态要求使用同一实例,对象需要同步,或者对象的创建成本很高,那么限定作用域是恰当的做法。

当然,当您需要限定作用域时,您可以使用 Hilt 中的作用域注解,也可以直接使用 Android Framework。

优秀的个人博客,低调大师

微信关注我们

原文链接:https://my.oschina.net/androiddevs/blog/4717167

转载内容版权归作者及来源网站所有!

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

相关文章

发表评论

资源下载

更多资源
Mario,低调大师唯一一个Java游戏作品

Mario,低调大师唯一一个Java游戏作品

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Apache Tomcat7、8、9(Java Web服务器)

Apache Tomcat7、8、9(Java Web服务器)

Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache、Sun 和其他一些公司及个人共同开发而成。因为Tomcat 技术先进、性能稳定,而且免费,因而深受Java 爱好者的喜爱并得到了部分软件开发商的认可,成为目前比较流行的Web 应用服务器。

Eclipse(集成开发环境)

Eclipse(集成开发环境)

Eclipse 是一个开放源代码的、基于Java的可扩展开发平台。就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境。幸运的是,Eclipse 附带了一个标准的插件集,包括Java开发工具(Java Development Kit,JDK)。

Java Development Kit(Java开发工具)

Java Development Kit(Java开发工具)

JDK是 Java 语言的软件开发工具包,主要用于移动设备、嵌入式设备上的java应用程序。JDK是整个java开发的核心,它包含了JAVA的运行环境(JVM+Java系统类库)和JAVA工具。