将对象 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。