1. 从 Dagger 的实质说起
一言以蔽之, Dagger 的实质便是一棵 Component Tree (组件树)。
1.1 Component :依靠注入容器
component 是 Dagger 中的中心概念,咱们经过 @Component
注解界说并生成代码。component 作为依靠注入容器,身兼工厂、库房、物流三种角色于一身。Dagger 中的许多重要注解也是服务于它的这三个身份:
-
@Module
和@Provides
为 comopnent 装置了生产依靠方针所需的”工厂”; -
@Singleton
等效果域注解将依靠以单例形式存储在 component 这个“库房”中,被更多当地同享; -
@Inject
为 component 供给送货上门的“物流”的才能,标记被注入的方针的字段,将依靠注入其间。
以下是运用 Dagger 界说的 ApplicationComponent
,为它为 App 注入所需的 userRepo
成员。
1.2 Tree:对运用层级联系的反映
一个运用往往都有层级结构。例如一个 Android 项目中从上到下有 Application -> Activity -> Fragment
等多层,每层可访问方针的生命周期长度不同:
-
UserRepository
:服务整个Application
-
LoginViewModel
:只在LoginActivity
规模可见。
当咱们运用 Dagger 来办理这些依靠方针时,需求有相对应的 component 供给不同“保鲜期”的库房。
此外,由于依靠方针之间有依靠联系。例如 LoginViewModel
需求运用 UserRepository
,因而对应 component 也产生了承继联系,LoginActivityComponent
依靠 ApplicationComponent
的实例来构建自己的实例,component 之间形成父子联系,进而构成一棵组件树。
2. 运用 @Subcomponent 构建组件树
Dagger 运用 @Subcomponent
界说子组件进而形成组件树。
2.1 界说子组件
在组件的界说上,@Subcomponent
和 @Component
没有差异,需求以此声明组件依靠的 module(非必须),注入的方针,以及创立子组件所需求的工厂。此外还需求一个自界说效果域注解 @ActivityScope
,它是 @Scope
的派生类,标明当时子组件的生命周期。
2.2 树立组件父子联系
子组件不能直接相关父组件,需求凭借 Module 装置到父组件。
如上,经过 SubcomponentsModule
子组件 LoginActivityComponent
被装置到父组件 ApplicationComponent
中,一起父组件中声明晰子组件的工厂,意味着父组件能够创立子组件。
2.3 运用子组件注入
由于组件之间有承继联系,子组件或需求依靠父组件构建自己的示例。因而,咱们不能随便构建子组件,需求经过父组件来构建,树立内涵依靠联系
如上,在合适的时机,LoginActivity
经过父组件供给的工厂创立子组件,完结对本身的注入。
2.4 Boilerplate 问题
前面的介绍能够感遭到,@Subcomponent
构建组件树过程中带来了比较多的模板代码:
- 子组件中供给
Subcomponent.Factory
- 父组件中需求声明子组件的工厂
- 合适的时机经过父组件创立子组件完结注入。
随着项目中的 activity 、fragment 等越来越多,上述相似的代码会重复出现,影响咱们运用 Dagger 的积极性。为了解决这个问题 dagger.android 和 Hilt 相继问世。
3. Dagger.android:代码生成组件树
dagger.android 是 Dagger 针对 Android 项目推出的子项目,中心思想是经过代码生成 subcomponent,下降 Andorid 项目中的模板代码。dagger.android 是独立于 Dagger 的库,工程中需求单独依靠:
//raw dagger2
implementation 'com.google.dagger:dagger:2.x'
kapt 'com.google.dagger:dagger-compiler:2.x'
//dagger.android
implementation 'com.google.dagger:dagger-android:2.x'
implementation 'com.google.dagger:dagger-android-support:2.x'
kapt 'com.google.dagger:dagger-android-processor:2.x'
咱们看一下引进 dagger.android 后的效果:
要害改变是新增了 @ContributesAndroidInjector
注解,它标记了一个回来值为 LoginActivity
的办法,其意义是编译期生成 LoginActivity
对应的子组件。因而咱们无需再显现地经过 @Subcomponent
和 @Subcomponent.Factory
声明子组件了,SubcomponentsModule
中也无需增加 subcomponents
依靠。
3.1 @ContributesAndroidInjector 生成子组件
看一下 @ContributesAndroidInjector
生成的完好代码:
能够看到咱们少写的代码,这里都生成了。LoginActivitySubcomponent
是子组件, SubcomponentsModule_ContributesLoginActivity
是用来装置子组件的 module。
3.2 DispatchingAndroidInjector 供给组件映射
特别值得一提的是代码中有一个 bindAndroidInjectorFactory
办法并携带了 @IntoMap
, @ClassKey
等若干注解,它们能够编译期 Dagger 构建依靠链条的过程中,向 DispatchingAndroidInjector
类填充一个 map,即其 injectorFactories
成员:
透过 map 的泛型界说不难估测,它是 Android class 与其对应子组件的工程的映射表,详细到前面 LoginActivity
的例子中,会填入 LoginActivity.class to LoginActivitySubcomponent.Factory
到 map 中
咱们在 App 中声明 androidInjector
成员,并经过 Dagger 注入 DispatchingAndroidInjector
实例。App 经过 HasAndroidInjector
接口对外宣称自己持有一个 androidInjector
,能够为各个 Activity 供给注入。
如上,在 LoginActivity
中,咱们不再需求经过父组件的工厂创立子组件,调用一个 AndroidInjection.inject
静态办法即可完结注入。静态办法内部会向上寻找 HasAndroidInjector
,然后经过映射表创立注入所需的子组件。
当然享受便当的一起,也要付出责任,LoginActivity
也需求完结 HasAndroidInjector
,并声明 androidInjector
,向下为它的 fragment 们供给注入。
3.3 dagger.android 的问题
dagger.andriid 首要做了下面两件事,帮咱们减少了模板代码 :
- 经过 @ContributesAndroidInjector 生成 subcomponent 及其 factory,省去了咱们显现地界说子组件,父组件也不需求再声明 Subcomponent.Factory
- 让 Android 各层级方针持有 AndroidInjector,经过静态办法完结对低层级方针的注入,省去了显现地创立子组件完结注入
dagger.android 尽管做出上述改善,可是代价是引进了一些新的模板代码 :
- 需求装备 @ContributesAndroidInjector
- Android 组件需求完结 HasAndroidInjector 接口,并注入 AndroidInjector 成员
- 需求手动调用 AndroidInjection.inject
4. Hilt:预界说组件树
dagger.android 没有存在模板代码,所以诞生了 Hilt,后者的思想是经过 “预界说” 的方法完全消除模板代码。
plugins {
id 'kotlin-kapt'
id 'com.google.dagger.hilt.android'
}
dependencies {
implementation "com.google.dagger:hilt-android:2.x"
kapt "com.google.dagger:hilt-compiler:2.x"
}
4.1 预界说 Component
相对于 dagger.android 帮咱们生成 LoginActivitySubComponent
, Hilt 中索性不允许自界说的 subcomponent,供给了预界说的 ActivityComponent
作为一切 activity 同享的供给注入的组件。而 LoginActivityModule
等原本装置到 LoginActivitySubComponent
的依靠,经过 @installIn
注解装置到 ActivityComponent
中。
ActivityComponent
是个 interface,编译期生成完结类 ActivityC
:
modules 中能够看到各 activity 依靠的 XXXActivityModule
都被 instllIn
中 ActivityC
中,ActivityC
能够为一切 activity 供给注入。
FragmentCBuilderModule
和 ViewCBuilderModule
用来装置 Hilt 别的两个预界说组件 FragmentComponent
和 ViewComponent
,Hilt 为 Android 中的要害概念都供给了对应的预界说组件,且将它们树立树行联系。
4.2 预界说 Inject
dagger.android 经过供给静态办法注入下降了 inject 的本钱。而在 Hilt 中,inject 的本钱趋近于零,只需求在 activity 等 Android 组件增加 @AndroidEntryPoint
注解,其他什么都不用做。
前面看到 ActivityC
完结了 XXXActivity_GeneratedInjector
接口,这些接口便是 @AndroidEntryPoint
的产品
LoginActivity_GeneratedInjector
供给了面向 LoginActivity
的 inject()
办法,并经过 @EntryPoint
装置到 ActivityComponent
中,这样编译后 ActivityC
就具备了 injectLoginActivity
的才能。
@EntryPoint 是 Hilt 的重要注解,由于咱们无法在 Hilt 的预界说 Component 中增加 inject 办法,所以当咱们期望 Hilt 为自界说类供给注入时,能够自界说 inject 接口,经过
@EntryPoint
装置到 Hilt 的预界说组件中。@AndroidEntryPoint
只不过是针对 Android 类提前生成了@EntryPoint
代码。
那么 LoginActivity
是什么时候调用 ActivityC
的 injectLoginActivity
完结本身注入的呢?Hilt 会为 LoginActivity
生成一个 activity 的派生类,它在合适的时间点调用 inject()
,内部会调用 ActivityC#injectLoginActivity
而 Hilt 经过 Transform + ASM,让编译后 LoginActivity
承继了 Hilt_LoginActivity
,这样就能够在不写任何代码的情况下,让 LoginActivity
基于 Hilt 的 ActivityC
完结注入,即所谓的 “预界说 inject”。
4.3 预界说 @Scope
Dagger 在 Android 中运用时,往往需求需求自界说效果域注解标明不同 Android 类的生命周期。Hilt 伴随着预界说组件,也供给了与之对应的预界说效果域注解
例如,增加了 @ActivityScoped
注解,表示 provides
的方针在 ActivityComponent
规模内以单例存在。
Hilt 为一切的要害的 Android 类都供给了预界说组件和相对应的预界说效果域注解,所以也能够说 Hilt 对整棵组件树进行了预界说:
5. 渐进式迁移
经过前面介绍,咱们能感遭到 Hilt 对 Dagger 的 Boilerplate 问题进行了比较完全的改善,主张咱们赶快晋级到 Hilt。从组件树的视角来了解 Dagger 与 Hilt 的差异,能够帮助咱们完结渐进式的晋级。
最安全的晋级过程便是从沿着组件树的树干,依照 Application -> Activity -> Fragment -> ...
的顺序,将 Dagger 的自界说组件兼并到 Hilt 的预界说组件,最终完结依靠注入完全托管到 Hilt 树。
5.1 将 Dagger 组件兼并到 Hilt
咱们以 ApplicationComponent
为例看一下,看一下 Dagger 中自界说 ApplicationComponent
怎么兼并到 Hilt 的预界说的 SingletonComponent
。
兼并后的代码如下所示,中心是 @EntryPoint
的运用:
-
@EntryPoint
让SingletonC
完结了ApplicationComponent
接口,代码中其他依靠ApplicationComponent
的当地能够无缝切换到SingletonComponent
,两棵树在根节点完结兼并。 - 新界说一个 module,经过
includes
将原本ApplicationComponent
的依靠打包装置到SingletonComponent
。
如上,在 App 中增加 @HiltAndroidEntryPoint
注解,Hilt 能够为 App 供给注入服务。可是 App 的 component 成员不能当即删去,或许还有其他代码在引用它。可是在根节点兼并后,咱们能够经过 EntryPointAccessors
从 Hilt 获取 ApplicationComponent
的完结。
其他层级的组件,例如 ActivityComponent
, FragmemtComponent
等,也能够模仿 ApplicationComponent
向 Hilt 做兼并。如果你运用的是 dagger.android,则不再需求 @ContributesAndroidInjector
生成预界说组件了,能够删去相关代码,将依靠的 module 装置到 Hilt 的对应组件,如下:
5.2 整理 Dagger 残留代码
当咱们将组件树上的一切组件都兼并到 Hilt,Android 类都转向经过 Hilt 获取依靠注入,所以 Dagger 或许 dagger.android 相关的代码都能够整理掉了
如上,以 App 为例,所以 Dagger 或许 dagger.android 相关的注入代码都能够删去了,代码清爽多了。
6. 预界说组件的问题
Hilt 的预界说组件在下降代码复杂度的一起,也丧失了自界说组件的灵活性,咱们来看两个常见问题及应对计划
6.1 区别详细 activity 类型
Dagger 依靠图中或许有对当时详细 activity 类型的依靠。通常咱们像下面这样,在创立 LoginActivityComponent
时为 Dagger 传入 activity 实例。
@ActivityScope
@Subcomponent
interface LoginActivityComponent {
@Subcomponent.Factory
interface Factory {
fun create(@BindsInstance activity: LoginActivity): LoginActivityComponent
}
}
Hilt 中没有机会创立自界说组件,该怎么供给不同类型的 activity 依靠呢。
预界说组件 ActivityComponent
中默许供给了当时 activity 的依靠,可是不区别详细类型,咱们能够经过不同类型的 module 强转 activity 为详细类型后供给出去,代码如下:
@InstallIn(ActivityComponent::class)
@Module
class LoginActivityModule {
@Provides
fun providesLoginActivity(activity: Activity): LoginActivity> =
activity as? LoginActivity?
}
可是必须说一句,对详细 activity 类型的依靠并非一个好规划,这意味着 activity 或许违反了单一责任的规划原则。
6.2 依据方针 activity 供给同一接口的不同完结
比如下面代码中,咱们能够为不同的 activity 组件供给不同 module,从而供给 LoginService
的不同完结。
interface LoginActivityModule {
@ActivityScope
@ContributesAndroidInjector(modules = [EmailModule::class])
fun contributesEmailLoginActivity(): EmailLoginActivity
@ActivityScope
@ContributesAndroidInjector(modules = [PhoneModule::class])
fun contributesPhoneLoginActivity(): PhoneLoginActivity
}
@Module
interface EmailModule {
@Binds
fun bindsService(service: EmailLoginService): LoginService
}
@Module
interface PhoneModule {
@Binds
fun bindsService(service: PhoneLoginService): LoginService
}
而 Hilt 中,两个 module 都会装置到同一个预界说组件 ActivityComponent
中,LoginService
也只能有一个完结。解决办法跟前面相似,也是依据当时 activity 类型,动态回来不同的 LoginService
:
@InstallIn(ActivityComponent::class)
@Modules
class LoginModule {
@Provides
fun providesService(activity: Activity): LoginService =
when(activity) {
is EmailLoginActivity -> EmailLoginService()
is PhoneLoginActivity -> PhoneLoginService()
else -> error("Invalid Activity")
}
}
7. 总结
当咱们认清了 Dagger 的实质是一颗组件树这一现实之后,能够更好地了解 dagger.android 和 Hilt 诞生的意图,都是经过不同方法下降组件树的构建本钱,前者挑选了代码生成的方法,后者挑选了预界说的方法。
Hilt 的预界说组件尽管牺牲了一定的灵活性,可是最大限度的下降了组件树的构建本钱。如果你想引进 DI 结构可是一向苦恼于 Dagger 的运用本钱,那么 Hilt 一定能满足你的需求,快用起来吧 ~
本文视频:www.bilibili.com/video/BV1cg…