Jetpack 新成员 Hilt 与 Dagger 大不同(三)落地篇

简介: 在 Google 的 Hilt 文档中 Dependency injection with Hilt 只是简单的告诉我们 Hilt 是 Android 的依赖注入库,它减少了在项目中进行手动依赖,Hilt 是基于 Dagger 基础上进行开发的,为常见的 Android 类提供容器并自动管理它们的生命周期等等。

image.png


在 Google 的 Hilt 文档中 Dependency injection with Hilt 只是简单的告诉我们 Hilt 是 Android 的依赖注入库,它减少了在项目中进行手动依赖,Hilt 是基于 Dagger 基础上进行开发的,为常见的 Android 类提供容器并自动管理它们的生命周期等等。


文档中的概念过于模糊,那么 Hilt 与 Dagger 在使用上有那些区别,并没有一个直观感受,而本文的目的就是详细的分析一下 Hilt 与 Dagger 到底有那些不同之处。


在之前的两篇文章中已经详细的介绍了 Hilt 注解的含义以及用法,并附上详细的案例,在代码中都有详细的注释,为了节省篇幅,本文不会在详细介绍 Hilt 注解的含义,可以点击下方链接前往查看。



在之前的文章中 放弃 Dagger 拥抱 Koin 分析了 Dagger 和 Koin 编译时间和使用上的不同等等,这篇文章主要从以下几个方面分析 Hilt 与 Dagger 的不同之处。


  • 初始化对比?
  • 与 Android 框架类对比?
  • 与 Room、WorkManager 对比?
  • 与 ViewModule 对比?
  • Hilt 在多模块中的局限性?


初始化对比



无论使用 Hilt 还是使用 Dagger,使用它们之前都需要在 Application 里面进行初始化,这是依赖注入容器的入口。


Dagger


在 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


在 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。


与 Android 框架类对比



我们来看一下 Dagger 和  Hilt 对于最常见的 Android 类 Application、Activity、Fragment、View、Service、BroadcastReceiver 的支持,我们以 Activity 和 Fragment 为例。


Dagger


在 Dagger 中对于每一个 Activity 和 Fragment 都需要告诉 Dagger 如何注入它们,所以我们需要创建对应的 ActivityModuleFragmentModule


每次有新增的 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


在 Hilt 中 Android 框架类完全由 Hilt 帮我管理,我们只要在 Activiyt 和 Fragmetn 中添加 @AndroidEntryPoint 即可。


@AndroidEntryPoint
class HitAppCompatActivity : AppCompatActivity() {
}
@AndroidEntryPoint
class HiltFragment : Fragment() {
}


Hilt 真做了很多优化工作,相比于 Dagger 而言,删除很多模板代码,不需要开发者手动管理,开发者只需要关注如何进行绑定即可。


与 Room、WorkManager 对比



接下来我们来看一下 Dagger 和 Hilt 对于 Room、WorkManager 在使用上有什么区别,这里以 Room 为例。


Dagger


在 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


在 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


与 ViewModule 对比?



我们在 Android 组件中注入一个 ViewModels 实例,需要通过 ViewModelFactory 来绑定 ViewModels 实例,传统的调用方式如下所示:


ViewModelProviders.of(this).get(DetailViewModel::class.java)


接下来我们来看一下在 Dagger 和 Hilt 中如何使用 ViewModule。


Dagger


在 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


Hilt 为我们提供了 @ViewModelInject 注解来注入 ViewModel 实例,另外 Hilt 为 SavedStateHandle 类提供了 @Assisted 注解来注入 SavedStateHandle 实例。


class HiltViewModel @ViewModelInject constructor(
    private val tasksRepository: Repository,
    //SavedStateHandle 用于进程被终止时,存储和恢复数据
    @Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel()


Hilt 的局限性



  • Hilt 不支持 ContentProvider,如果你在想在 ContentProvider 中获取 Hilt 提供的依赖,需要使用 @EntryPoint 注解。具体如何使用,可以看之前的内容 在 Hilt 不支持的类中执行依赖注入
  • Hilt 在多模块项目中的局限性,多模块项目大概分为两种类型:
  • 分级模块组成的应用程序。
  • 动态功能模块(dynamic feature modules)组成的应用程序。


分级模块组成的应用程序


如果多模块项目是由分级模块组成的应用程序,那么可以使用 Hilt 来完成依赖注入,分级模块依赖如下图所示。图来自 Google。


image.png


从上到下层层依赖,这种情况下可以直接使用 Hilt 进行依赖注入,和在单个 App 模块中使用是一样的,这里不再详述了,Hilt 在多模块中的使用的项目示例

HiltWithMultiModuleSimple 已经上传到 GitHub 上了,代码中有详细的注释。


动态功能模块应用程序


如果是模块项目是 dynamic feature modules (动态功能模块)组成的应用程序,那么使用 Hilt 就有些局限性了,dynamic feature modules 简称 DFMs。


在 DFMs 中,模块之间相互依赖的方式是颠倒的,因此 Hilt 无法在动态功能模块中使用,所以在 DFMs 中只能使用 Dagger 完成依赖注入,在 DFMs 中模块依赖如下图所示。图来自 Google。


image.png


一个 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 中组件依赖问题?


  1. 在 app module 中或者任何其它可以被 Hilt 处理的模块中,定义一个接口并添加 @EntryPoint 注解,然后添加 @InstallIn 注解指定 module 的范围。


// LoginModuleDependencies.kt - File in the app module.
@EntryPoint
@InstallIn(ApplicationComponent::class)
interface LoginModuleDependencies {
  @AuthInterceptorOkHttpClient
  fun okHttpClient(): OkHttpClient
}
复制代码


       b.在 dynamic-feature1 模块中,使用 Dagger 中的 @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 方式一样。


  • 在 Application 中 初始化 Dagger,并实现 HasActivityInjector 接口。
  • 对于每一个 ViewModel、Fragment 和 Activity 我们需要告诉 Dagger 如何注入它们。
  • 在每个 modules 中添加 Fragments、Activities 和 ViewModels。
  • 所有的 Activity 继承 BaseActivity,我们需要实现 HasSupportFragmentInjector。


总结



关于 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、Paging3、Hilt 等等,正在逐渐增加其他 Jetpack 新成员,仓库持续更新,可以前去查看:AndroidX-Jetpack-Practice, 如果这个仓库对你有帮助,请帮我点个赞,我会陆续完成更多 Jetpack 新成员的项目实践。


结语



致力于分享一系列 Android 系统源码、逆向分析、算法、翻译、Jetpack 源码相关的文章,正在努力写出更好的文章,如果这篇文章对你有帮助给个 star,一起来学习,期待与你一起成长。


算法


由于 LeetCode 的题库庞大,每个分类都能筛选出数百道题,由于每个人的精力有限,不可能刷完所有题目,因此我按照经典类型题目去分类、和题目的难易程度去排序。


  • 数据结构: 数组、栈、队列、字符串、链表、树……
  • 算法: 查找算法、搜索算法、位运算、排序、数学、……


每道题目都会用 Java 和 kotlin 去实现,并且每道题目都有解题思路、时间复杂度和空间复杂度,如果你同我一样喜欢算法、LeetCode,可以关注我 GitHub 上的 LeetCode 题解:Leetcode-Solutions-with-Java-And-Kotlin,一起来学习,期待与你一起成长。


Android 10 源码系列


正在写一系列的 Android 10 源码分析的文章,了解系统源码,不仅有助于分析问题,在面试过程中,对我们也是非常有帮助的,如果你同我一样喜欢研究 Android 源码,可以关注我 GitHub 上的 Android10-Source-Analysis,文章都会同步到这个仓库。



Android 应用系列



精选译文


目前正在整理和翻译一系列精选国外的技术文章,不仅仅是翻译,很多优秀的英文技术文章提供了很好思路和方法,每篇文章都会有译者思考部分,对原文的更加深入的解读,可以关注我 GitHub 上的 Technical-Article-Translation,文章都会同步到这个仓库。



工具系列




目录
相关文章
Jetpack Compose中ViewModel、Flow、Hilt、Coil的使用
Jetpack Compose中ViewModel、Flow、Hilt、Coil的使用
1218 0
Jetpack Compose中ViewModel、Flow、Hilt、Coil的使用
|
2月前
|
编译器 Android开发 开发者
带你了解Android Jetpack库中的依赖注入框架:Hilt
本文介绍了Hilt,这是Google为Android开发的依赖注入框架,基于Dagger构建,旨在简化依赖注入过程。Hilt通过自动化的组件和注解减少了DI的样板代码,提高了应用的可测试性和可维护性。文章详细讲解了Hilt的主要概念、基本用法及原理,帮助开发者更好地理解和应用Hilt。
72 8
|
测试技术 数据库 Android开发
Android Jetpack 浅析Hilt依赖注入
首先,某个类的成员变量称为依赖,如若此变量想要实例化引用其类的方法,可以通过构造函数传参或者通过某个方法获取对象,此等通过外部方法获取对象实例的称为依赖注入;而依赖注入又可以简单分为`手动注入`和`自动注入`两种方式;`Hilt`就是基于Dagger进行`场景化优化`的一个依赖注入库,Hilt是Google专门为Android平台打造的一个依赖注入库,在使用上极大程度进行啦简化(与dagger相比)
405 1
|
Android开发 开发者 容器
上手指南 | Jetpack Hilt 依赖注入框架
上手指南 | Jetpack Hilt 依赖注入框架
上手指南 | Jetpack Hilt 依赖注入框架
|
API 开发工具 Android开发
Jetpack新成员——Glance
Jetpack新成员——Glance
217 0
Jetpack新成员——Glance
|
API Android开发 开发者
Jetpack 叒一新成员 DragAndDrop 框架:大大简化拖放手势开发
Jetpack 叒一新成员 DragAndDrop 框架:大大简化拖放手势开发
Jetpack 叒一新成员 DragAndDrop 框架:大大简化拖放手势开发
|
API Android开发
Jetpack新成员SplashScreen:为全新的应用启动效果赋能!(2)
Jetpack新成员SplashScreen:为全新的应用启动效果赋能!(2)
Jetpack新成员SplashScreen:为全新的应用启动效果赋能!(2)
|
开发框架 API 开发工具
Jetpack新成员SplashScreen:为全新的应用启动效果赋能!(1)
Jetpack新成员SplashScreen:为全新的应用启动效果赋能!(1)
Jetpack新成员SplashScreen:为全新的应用启动效果赋能!(1)
|
Java API 调度
Jetpack Hilt有哪些改善又有哪些限制?
Jetpack Hilt有哪些改善又有哪些限制?
Jetpack Hilt有哪些改善又有哪些限制?