跨越 IOC容器的壁垒
使用依赖注入(DI)时,我们需要它对 实例
、依赖关系
、 生命周期
进行管理,因此DI框架会构建一个容器,用于实现这些功能。这个容器我们惯称为IOC容器。
在容器中,会按照我们制定的规则:
- 创建实例
- 访问实例
- 注入依赖
- 管理生命周期
但容器外也有访问容器内部的需求,显然这里存在一道虚拟的 边界、壁垒
。这种需求分为两类:
- 依赖注入客观需要的入口
- 系统中存在合理出现的、非DI框架管理的实例,但它不希望破坏其他实例对象的
生命周期
、作用域唯一性
,即它的依赖希望交由DI框架管理
但请注意,IOC容器内部也存在着 边界、壁垒
,这和它管理实例的机制有关,在Hilt(包括Dagger)中,最大颗粒度的内部壁垒是 Component
。
即便从外部突破IOC容器的壁垒,也只能进入某个特定的Component
使用EntryPoint跨越IOC容器壁垒
在Hilt中,我们可以很方便地
- 使用接口定义 进入点(EntryPoint),并使用
@EntryPoint
注解使其生效; - 用
@InstallIn
注解指明访问的Component; - 并利用
EntryPoints
完成访问,突破容器壁垒
下面的代码展示了如何定义:
UserComponent是自定义的Component,在下文中会详细展开
@EntryPoint
@InstallIn(UserComponent::class)
interface UserEntryPoint {
fun provideUserVO(): UserVO
}
下面的代码展示了如何获取进入点,注意,您需要先获得对应的Component实例。
对于Hilt内建的Component,均有其获取方法,而自定义的Component,需从外界发起生命周期控制,同样会预留实例访问路径
fun manualGet(): UserEntryPoint {
return EntryPoints.get(
UserComponentManager.instance.generatedComponent(),
UserEntryPoint::class.java
)
}
当获取进入点后,即可使用预定义的API,访问容器内的对象实例。
自定义Scope、Component
部分业务场景中,Hilt内建的Scope和Component并不能完美支持,此时我们需要进行自定义。
为了下文能够更顺利的展开,我们再花一定的笔墨对 Scope
、Component
、Module
的含义进行澄清。
Scope、Component、Module的真实含义
前文提到两点:
- DI框架需要
创建实例
、访问实例
、注入依赖
、管理生命周期
- IOC容器内部也存在着
边界、壁垒
,这和它管理实例的机制有关,在Hilt(包括Dagger)中,最大颗粒度的内部壁垒是Component
。
不难理解:
- 实例之间,也会存在依赖关系;
- DI框架需要管理内部实例的生命周期;
- 需要进行依赖注入的客户,本身也存在生命周期,它的依赖对象,应该结合实际需求被合理控制生命周期,避免生命周期泄漏;
因此,出现了 范围、作用域
即 Scope
的概念,它包含两个维度:实例的生命周期范围;实例之间的访问界限。
并且DI框架通过Component控制内部对象的生命周期。
举一个例子描述,以Activity为例,Activity需要进行依赖注入,并且我们不希望Activity自身需要的依赖出现生命周期泄漏,于是按照Activity的生命周期特点定义了:
ActivityRetainedScoped
ActivityRetainedComponent
,不受reCreate 影响ActivityScoped
、ActivityComponent
,横竖屏切换等配置变化引起reCreate 开始新生命周期
并据此对 依赖对象实例
实施 生命周期
和 访问范围
控制
可以记住以下三点结论:
- Activity实例按照
预定Scope对应的生命周期范围
创建、管理Component,访问Component中的实例; - Component内的实例可以互相访问,实例的生命周期和Component一致;
- Activity实例(需要依赖注入的客户)和 Component中的实例 可以访问
父Component
中的实例,父Component的生命周期完全包含子Component的生命周期
内建的Scope、Component关系参考:
而Module指导DI框架 创建实例
、选用实例进行注入
值得注意的是,Hilt(以及Dagger)可以通过 @Inject
注解类构造函数指导 创建实例
,此方式创建的实例的生命周期跟随宿主,与 通过Module方式
进行对比,存在生命周期管理粒度上的差异。
自定义
至此,已不难理解:因为有实际的生命周期范围管理需求,才会自定义。
为了方便行文以及编写演示代码,我们举一个常见的例