在 Google 的 Hilt 文档中 Dependency injection with Hilt 只是简单的告诉咱们 Hilt 是 Android 的依赖注入库,它减小了在项目中进行手动依赖,Hilt 是基于 Dagger 基础上进行开发的,为常见的 Android 类提供容器并自动管理它们的生命周期等等。java
文档中的概念过于模糊,那么 Hilt 与 Dagger 在使用上有那些区别,并无一个直观感觉,而本文的目的就是详细的分析一下 Hilt 与 Dagger 到底有那些不一样之处。android
在以前的两篇文章中已经详细的介绍了 Hilt 注解的含义以及用法,并附上详细的案例,在代码中都有详细的注释,为了节省篇幅,本文不会在详细介绍 Hilt 注解的含义,能够点击下方连接前往查看。git
在以前的文章中 放弃 Dagger 拥抱 Koin 分析了 Dagger 和 Koin 编译时间和使用上的不一样等等,这篇文章主要从如下几个方面分析 Hilt 与 Dagger 的不一样之处。github
不管使用 Hilt 仍是使用 Dagger,使用它们以前都须要在 Application 里面进行初始化,这是依赖注入容器的入口。面试
在 Dagger 中咱们必须经过 @Module
和 @Component
注解,建立对应的文件,并注入 Application算法
// Component 声明了全部的 modules
// ActivityAllModule 配置了全部的 activity
@Singleton
@Component(modules = arrayOf(
AndroidInjectionModule::class,
ActivitylModule::class))
interface AppCompoment {
fun inject(app: App)
@Component.Builder
interface Builder {
@BindsInstance
fun bindApplication(app: Application): Builder
fun build(): AppCompoment
}
}
复制代码
而后建立完 modules 和 components 文件以后,须要在 Application 中 初始化 Dagger, 须要实现 HasActivityInjector
接口,用来自动管理 Activity。数据库
class App : Application(), HasActivityInjector {
@Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
}
override fun onCreate() {
super.onCreate()
DaggerAppCompoment.builder()
.bindApplication(this)
.build()
.inject(this)
}
override fun activityInjector(): AndroidInjector<Activity> {
return dispatchingAndroidInjector
}
}
复制代码
在 Hilt 中咱们不须要手动指定包含每一个模块,在 Application 中添加 @HiltAndroidApp
注解将会触发 Hilt 代码的生成,用做应用程序依赖项容器的基类。编程
@HiltAndroidApp
class HiltApplication : Application() {
/**
* 1. 全部使用 Hilt 的 App 必须包含一个使用 @HiltAndroidApp 注解的 Application
* 2. @HiltAndroidApp 将会触发 Hilt 代码的生成,包括用做应用程序依赖项容器的基类
* 3. 生成的 Hilt 组件依附于 Application 的生命周期,它也是 App 的父组件,提供其余组件访问的依赖
* 4. 在 Application 中设置好 @HiltAndroidApp 以后,就可使用 Hilt 提供的组件了,
* Hilt 提供的 @AndroidEntryPoint 注解用于提供 Android 类的依赖(Activity、Fragment、View、Service、BroadcastReceiver)等等
* Application 使用 @HiltAndroidApp 注解
*/
}
复制代码
全部使用 Hilt 的 App 必须包含一个使用 @HiltAndroidApp
注解的 Application,这是依赖注入容器的入口。数组
Hilt 提供了 @ApplicationContext
和 @ActivityContext
两种预约义限定符,咱们能够直接使用,不须要开发人员本身注入 Application。性能优化
咱们来看一下 Dagger 和 Hilt 对于最多见的 Android 类 Application、Activity、Fragment、View、Service、BroadcastReceiver 的支持,咱们以 Activity 和 Fragment 为例。
在 Dagger 中对于每个 Activity 和 Fragment 都须要告诉 Dagger 如何注入它们,因此咱们须要建立对应的 ActivityModule
、FragmentModule
。
每次有新增的 Fragment 和 Activity 必须添加在对应的 Module 文件中,每次添加 Activity 时都须要添加 @ContributesAndroidInjector
注解,用于自动生成子组件相关代码,帮咱们减小重复的模板代码,编译的时候会自动建立的一个类 ActivitylModule_ContributeXXXXActivity
,帮咱们生成注入的代码。
// 把全部的 Activity 放到 ActivitylModule 进行统一管理
@Module
abstract class ActivitylModule(){
// ContributesAndroidInjector 用于自动生成子组件相关代码,帮咱们减小重复的模板代码
// modules 指定子组件(当前 MainActivity 包含了 2 个 fragment,因此咱们须要指定包含的 fragment)
// 每次新建的 activity 都须要在这里手动添加
// 经过注解 @ActivityScope 指定 Activity 的 生命周期
@ActivityScope
@ContributesAndroidInjector(modules = arrayOf(FragmentModule::class))
abstract fun contributeMainActivity():MainActivity
}
// 管理全部的 Fragment
@Module
abstract class FragmentModule {
// 若是当前有新增的 Fragment 须要添加到这个模块中
@ContributesAndroidInjector
abstract fun contributeHomeFragment(): HomeFragment
@ContributesAndroidInjector
abstract fun contributeAboutFragment(): AboutFragment
}
复制代码
Dagger 提供了 HasSupportFragmentInjector
接口去自动管理 Fragment,全部的 Activity 继承 BaseActivity,咱们须要实现 HasSupportFragmentInjector
,而且须要在 Activity 和 Fragment 中添加 AndroidInjection.inject(this)
。
abstract class BaseActivity : AppCompatActivity(),HasSupportFragmentInjector {
@Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
}
override fun supportFragmentInjector(): AndroidInjector<Fragment> {
return dispatchingAndroidInjector
}
}
abstract class BaseFragment : Fragment() {
override fun onAttach(context: Context?) {
super.onAttach(context)
AndroidSupportInjection.inject(this)
}
}
复制代码
在 Hilt 中 Android 框架类彻底由 Hilt 帮我管理,咱们只要在 Activiyt 和 Fragmetn 中添加 @AndroidEntryPoint
便可。
@AndroidEntryPoint
class HitAppCompatActivity : AppCompatActivity() {
}
@AndroidEntryPoint
class HiltFragment : Fragment() {
}
复制代码
Hilt 真作了不少优化工做,相比于 Dagger 而言,删除不少模板代码,不须要开发者手动管理,开发者只须要关注如何进行绑定便可。
接下来咱们来看一下 Dagger 和 Hilt 对于 Room、WorkManager 在使用上有什么区别,这里以 Room 为例。
在 Dagger 中使用 Room 须要使用 @Module
注解建立 RoomModule 文件,而后在 Component 中添加 RoomModule
。
@Module
class RoomModule(val app: Application) {
@Provides
@Singleton
fun providerAppDataBase(): AppDataBase = Room
.databaseBuilder(app, AppDataBase::class.java, "dhl.db")
.fallbackToDestructiveMigration()
.allowMainThreadQueries()
.build()
}
// Component 声明了全部的 modules
// ActivityAllModule 配置了全部的 activity
// RoomModule 和数据库相关的
@Singleton
@Component(modules = arrayOf(
AndroidInjectionModule::class,
RoomModule::class,
ActivitylModule::class))
interface AppCompoment {
fun inject(app: App)
@Component.Builder
interface Builder {
@BindsInstance
fun bindApplication(app: Application): Builder
fun build(): AppCompoment
}
}
复制代码
在 Dagger 中须要在对应的模块中添加组件对应的生命周期 @Singleton
、@ActivityScope
等等。
@Singleton
对应的 Application。@ActivityScope
对应的 Activity。在 Hilt 中咱们只须要使用注解 @Module
建立 RoomModule
文件便可,不须要本身手动去添加 Module。
使用 @InstallIn
注解指定 module 的生命周期,例如使用 @InstallIn(ApplicationComponent::class)
注解 module 会绑定到 Application 的生命周期上。
@Module
@InstallIn(ApplicationComponent::class)
// 这里使用了 ApplicationComponent,所以 RoomModule 绑定到 Application 的生命周期。
object RoomModule {
/**
* @Provides 经常使用于被 @Module 注解标记类的内部的方法,并提供依赖项对象。
* @Singleton 提供单例
*/
@Provides
@Singleton
fun provideAppDataBase(application: Application): AppDataBase {
return Room
.databaseBuilder(application, AppDataBase::class.java, "dhl.db")
.fallbackToDestructiveMigration()
.allowMainThreadQueries()
.build()
}
}
复制代码
Hilt 提供了如下组件来绑定依赖与对应的 Android 类。
Hilt 提供的组件 | 对应的 Android 类 |
---|---|
ApplicationComponent | Application |
ActivityRetainedComponent | ViewModel |
ActivityComponent | Activity |
FragmentComponent | Fragment |
ViewComponent | View |
ViewWithFragmentComponent | View annotated with @WithFragmentBindings |
ServiceComponent | Service |
咱们在 Android 组件中注入一个 ViewModels 实例,须要经过 ViewModelFactory 来绑定 ViewModels 实例,传统的调用方式以下所示:
ViewModelProviders.of(this).get(DetailViewModel::class.java)
复制代码
接下来咱们来看一下在 Dagger 和 Hilt 中如何使用 ViewModule。
在 Dagger 中,对于每个 ViewModel,须要告诉 Dagger 如何注入它们,因此咱们须要建立 ViewModelModule
文件,在 ViewModelModule
中管理全部的 ViewModel。
@Module
abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(DetailViewModel::class)
abstract fun bindDetailViewModel(viewModel: DetailViewModel): ViewModel
@Binds
abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
}
复制代码
建立完 ViewModelModule
文件以后,须要在 Component 中添加 ViewModelModule
// Component 声明了全部的 modules
// RoomModule 和数据库相关的
// ActivityAllModule 配置了全部的 activity
// ViewModelModule 配置全部的 ViewModel
@Singleton
@Component(modules = arrayOf(
AndroidInjectionModule::class,
RoomModule::class,
ViewModelModule::class,
ActivitylModule::class))
interface AppCompoment {
fun inject(app: App)
@Component.Builder
interface Builder {
@BindsInstance
fun bindApplication(app: Application): Builder
fun build(): AppCompoment
}
}
复制代码
Hilt 为咱们提供了 @ViewModelInject
注解来注入 ViewModel 实例,另外 Hilt 为 SavedStateHandle
类提供了 @Assisted
注解来注入 SavedStateHandle
实例。
class HiltViewModel @ViewModelInject constructor(
private val tasksRepository: Repository,
//SavedStateHandle 用于进程被终止时,存储和恢复数据
@Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel()
复制代码
Hilt 不支持 ContentProvider
,若是你在想在 ContentProvider
中获取 Hilt 提供的依赖,须要使用 @EntryPoint
注解。具体如何使用,能够看以前的内容 在 Hilt 不支持的类中执行依赖注入
Hilt 在多模块项目中的局限性,多模块项目大概分为两种类型:
若是多模块项目是由分级模块组成的应用程序,那么可使用 Hilt 来完成依赖注入,分级模块依赖以下图所示。图来自 Google。
从上到下层层依赖,这种状况下能够直接使用 Hilt 进行依赖注入,和在单个 App 模块中使用是同样的,这里再也不详述了,Hilt 在多模块中的使用的项目示例 HiltWithMultiModuleSimple 已经上传到 GitHub 上了,代码中有详细的注释。
若是是模块项目是 dynamic feature modules (动态功能模块)组成的应用程序,那么使用 Hilt 就有些局限性了,dynamic feature modules 简称 DFMs。
在 DFMs 中,模块之间相互依赖的方式是颠倒的,所以 Hilt 没法在动态功能模块中使用,因此在 DFMs 中只能使用 Dagger 完成依赖注入,在 DFMs 中模块依赖以下图所示。图来自 Google。
一个 App 被分割成一个 Base APK 和多个模块 APK。
Base APK: 这个 APK 包含了基本的代码和资源(services, content providers, permissions)等等,其余被分割的模块 APK 均可以访问,当一个用户请求下载你的应用,这个 APK 首先下载和安装。
Configuration APK:每一个 APK 包含特定的资源。当用户下载你的应用程序时,他们的设备只下载和安装针对他们设备的 Configuration APK。
Dynamic Feature APK:每一个 APK 包含应用程序的某个功能的代码和资源,这些功能在首次安装应用程序时是不须要的。用户能够按需安装 Feature APK,从而为用户提供额外的功能。每一个 Feature APK 都依赖于 Base APK。
例如 dynamic-feature1 依赖于 Base APK,因此在 DFMs 中,模块之间相互依赖的方式是颠倒的。
如何解决 Hilt 在 DFMs 中组件依赖问题?
@EntryPoint
注解,而后添加 @InstallIn
注解指定 module 的范围。// LoginModuleDependencies.kt - File in the app module.
@EntryPoint
@InstallIn(ApplicationComponent::class)
interface LoginModuleDependencies {
@AuthInterceptorOkHttpClient
fun okHttpClient(): OkHttpClient
}
复制代码
@Component
注解,建立 Component 文件,并在 dependencies
中指定经过 @EntryPoint
注解声明的接口。@Component(dependencies = [LoginModuleDependencies::class])
interface LoginComponent {
fun inject(activity: LoginActivity)
@Component.Builder
interface Builder {
fun context(@BindsInstance context: Context): Builder
fun appDependencies(loginModuleDependencies: LoginModuleDependencies): Builder
fun build(): LoginComponent
}
}
复制代码
上面步骤完成以后,就能够在 DFMs 中使用 Dagger 完成依赖注入,就跟咱们以前介绍的使用 Dagger 方式同样。
关于 Hilt 在多模块中的使用的项目示例 HiltWithMultiModuleSimple 已经上传的 GitHub 能够前去查看。
到这里 Hilt 入门三部曲终于完结了,从入门、进阶、到落地,全部注解的含义以及项目示例、以及和 Jetpack 组件的使用,Hilt 与 Dagger 不一样之处,以及在多模块中局限性以及使用,所有都介绍完了。
对于使用 Dagger 小伙伴们,应该可以感觉到从入门到放弃是什么感受,Dagger 学习的成本是很是高的,若是项目中引入了 Dagger 意味着团队每一个人都要学习 Dagger,无疑这个成本是巨大的,并且使用起来很是的复杂。对于每个 ViewModel、Fragment 和 Activity 咱们须要告诉 Dagger 如何注入它们。
而 Hilt 的学习成本相对于 Dagger 而言成本很是低,Hilt 集成了 Jetpack 库和 Android 框架类,并自动管理它们的生命周期,让开发者只须要关注如何进行绑定,而不须要管理全部 Dagger 配置的问题。
正在创建一个最全、最新的 AndroidX Jetpack 相关组件的实战项目 以及 相关组件原理分析文章,目前已经包含了 App Startup、Paging三、Hilt 等等,正在逐渐增长其余 Jetpack 新成员,仓库持续更新,能够前去查看:AndroidX-Jetpack-Practice, 若是这个仓库对你有帮助,请帮我点个赞,我会陆续完成更多 Jetpack 新成员的项目实践。
致力于分享一系列 Android 系统源码、逆向分析、算法、翻译、Jetpack 源码相关的文章,正在努力写出更好的文章,若是这篇文章对你有帮助给个 star,一块儿来学习,期待与你一块儿成长。
因为 LeetCode 的题库庞大,每一个分类都能筛选出数百道题,因为每一个人的精力有限,不可能刷完全部题目,所以我按照经典类型题目去分类、和题目的难易程度去排序。
每道题目都会用 Java 和 kotlin 去实现,而且每道题目都有解题思路、时间复杂度和空间复杂度,若是你同我同样喜欢算法、LeetCode,能够关注我 GitHub 上的 LeetCode 题解:Leetcode-Solutions-with-Java-And-Kotlin,一块儿来学习,期待与你一块儿成长。
正在写一系列的 Android 10 源码分析的文章,了解系统源码,不只有助于分析问题,在面试过程当中,对咱们也是很是有帮助的,若是你同我同样喜欢研究 Android 源码,能够关注我 GitHub 上的 Android10-Source-Analysis,文章都会同步到这个仓库。
目前正在整理和翻译一系列精选国外的技术文章,不只仅是翻译,不少优秀的英文技术文章提供了很好思路和方法,每篇文章都会有译者思考部分,对原文的更加深刻的解读,能够关注我 GitHub 上的 Technical-Article-Translation,文章都会同步到这个仓库。