将对象 A 的做用域限定到对象 B,指的是对象 B 的整个生命周期内始终持有相同的 A 实例。当涉及到 DI (依赖项注入) 时,限定对象 A 的做用域为一个容器,则意味着该容器在销毁以前始终提供相同的 A 实例。android
在 Hilt 中,您能够经过注解将类型的做用域限定在某些容器或组件内。例如,您的应用中有一个处理登陆和注销的 UserManager
类型。您可使用 @Singleton
注解将该类型的做用域限定为 ApplicationComponent
(ApplicationComponent
是一个被整个应用的生命周期管理的容器)。被限定做用域的类型在应用组件中沿 组件层次结构 向下传递: 在本案例中,相同的 UserManager
实例将被提供给层次结构内其他的 Hilt 组件。应用中任何依赖于 UserManager
的类型都将得到相同的实例。架构
注意 : 默认状况下,Hilt 中的绑定都 未限定做用域 。这些绑定不属于任何组件,而且能够在整个项目中被访问。每次被请求都会提供该类型的不一样实例。当您将绑定的做用域限定为某个组件时,它会限制您使用该绑定的范围以及该类型能够具备的依赖项。
在 Android 中,您不使用 DI 库也能够经过 Android Framework 来手动限定做用域。让咱们看看如何手动限定做用域,以及如何改用 Hilt 来限定做用域。最后,咱们将比较使用 Android Framework 手动限定做用域和使用 Hilt 限定做用域的区别。框架
看了上文的定义,您可能会有这样的异议: 在某个特定类中使用一个类型的实例变量也能够作到限定该变量类型的做用域。没错!不使用 DI 时,您能够执行以下操做:ide
class ExampleActivity : AppCompatActivity() { private val analyticsAdapter = AnalyticsAdapter() ... }
analyticsAdapter
变量的做用域被限定为 MyActivity
的生命周期,这意味着只要 Activity 没有被销毁,该变量就是同一个实例。若是另外一个类出于某种缘由须要访问这个被限定了做用域的变量,每次访问也会得到相同实例。当新的 MyActivity
实例被建立时 (如系统设置改变),一个新的 AnalyticsAdapter
实例将会被建立。ui
使用 Hilt,等效代码以下:google
@ActivityScoped class AnalyticsAdapter @Inject constructor() { ... } @AndroidEntryPoint class ExampleActivity : AppCompatActivity() { @Inject lateinit var analyticsAdapter: AnalyticsAdapter }
每次建立的 MyActivity
都会持有一个 ActivityComponent
DI 容器的新实例,在 Activity 被销毁以前,该实例将向 组件层次结构 下的依赖项提供相同的 AnalyticsAdapter
实例。spa
更改系统设置后,您将得到一个新的 AnalyticsAdapter 和 MainActivity 实例code
然而,咱们可能但愿 AnalyticsAdapter
能够在系统设置更改后留存!或者说,咱们但愿直到用户离开 Activity 以前,都限定该实例的做用域为 Activity。component
为此,您可使用 组件架构中的 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 限定做用域,优点为您可在 Hilt 组件层次结构中使用被限定的类型;而对于 ViewModel,则必须经过 ViewModel 手动访问被限定做用域的类型。
使用 ViewModel 限定做用域,优点为您能够在应用中任何 LifecyclerOwner 对象中持有 ViewModel。例如,若是您使用了 Jetpack Navigation 库,则能够将 ViewModel 绑定到 NavGraph 上。
Hilt 提供的做用域数量有限。可能没有符合您特定使用场景的做用域。例如嵌套 Fragment,对于这种状况,您能够退一步使用 ViewModel 限定做用域。
如上文所述,您可使用 @ViewModelInject
向 ViewModel 注入依赖项。其原理是这些绑定关系保存在 ActivityRetainedComponent
中,这也是为何您只能注入未限定做用域的类型,或者是限定做用域为 ActivityRetainedComponent
以及 ApplicationComponent
的类型。
若是 Activity 或 Fragment 被 @AndroidEntryPoint
注解修饰,就能够经过 getDefaultViewModelProviderFactory()
方法获取 Hilt 生成的 ViewModel 工厂了。因为能够在 ViewModelProvider
中使用这些 ViewModel 工厂,使您获取 ViewModel 的方式变得更加灵活。例如: 将做用域限定为 BackStackEntry
的 ViewModel。
限定做用域会有一些代价,由于提供的对象在持有者被销毁以前将一直保留在内存中。请在应用中慎重地考虑使用限定做用域的对象。若是对象的内部状态要求使用同一实例,对象须要同步,或者对象的建立成本很高,那么限定做用域是恰当的作法。
固然,当您须要限定做用域时,您可使用 Hilt 中的做用域注解,也能够直接使用 Android Framework。