查看原文
其他

Jetpack 成员 Hilt 与 Dagger 区别 (三) 落地篇

hi-dhl ByteCode 2021-10-12

Hilt 系列文章:

前两篇文章主要  Hilt 注解的含义、如何在项目中使用、以及如何和 Jetpack 成员结合一起使用。

这篇文章作为一个概念了解即可,简单一句话概括:Hilt 简化了 Dagger 的使用,比 Dagger 要简单很多。

都说 Hilt 是基于 Dagger 基础上进行开发的,那么 Hilt 与 Dagger 有那些区别呢?

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

文档中的概念过于模糊,Hilt 与 Dagger 有那些区别,并没有一个直观感受,而这篇文章主要从以下几个方面分析 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 类
ApplicationComponentApplication
ActivityRetainedComponentViewModel
ActivityComponentActivity
FragmentComponentFragment
ViewComponentView
ViewWithFragmentComponentView annotated with @WithFragmentBindings
ServiceComponentService

与 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 注解。具体如何使用,可以看之前的文章  Jetpack 成员 Hilt 结合 App Startup(二)进阶篇

  • Hilt 在多模块项目中的局限性,多模块项目大概分为两种类型:

    • 分级模块组成的应用程序。
    • 动态功能模块(dynamic feature modules)组成的应用程序。

分级模块组成的应用程序

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

从上到下层层依赖,这种情况下可以直接使用 Hilt 进行依赖注入,和在单个 App 模块中使用是一样的,这里不再详述了,Hilt 在多模块中的使用的项目示例已经上传到 GitHub 欢迎前去查看 AndroidX-Jetpack-Practice/HiltWithMultiModuleSimple ,代码中有详细的注释。

https://github.com/hi-dhl/AndroidX-Jetpack-Practice

动态功能模块应用程序

如果是模块项目是 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 中组件依赖问题?

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

@EntryPoint
@InstallIn(ApplicationComponent::class)
interface LoginModuleDependencies {

  @AuthInterceptorOkHttpClient
  fun okHttpClient(): OkHttpClient
}
  • 在 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 在多模块中的使用的项目示例已经上传到 GitHub 欢迎前去查看 AndroidX-Jetpack-Practice/HiltWithMultiModuleSimple ,代码中有详细的注释。

https://github.com/hi-dhl/AndroidX-Jetpack-Practice

到这里 Hilt 入门三部曲,从入门、进阶、到落地,所有注解的含义以及项目示例、以及和 Jetpack 组件的使用,Hilt 与 Dagger 不同之处,以及在多模块中局限性以及使用,全部都介绍完了。

对于使用 Dagger 小伙伴们,应该能够感受到从入门到放弃是什么感觉,Dagger 学习的成本是非常高的,如果项目中引入了 Dagger 意味着团队每个人都要学习 Dagger,无疑这个成本是巨大的,而且使用起来非常的复杂。

而 Hilt 的学习成本相对于 Dagger 而言成本非常低,Hilt 集成了 Jetpack 库和 Android 框架类,并自动管理它们的生命周期,让开发者只需要关注如何进行绑定,而不需要管理所有 Dagger 配置的问题。



推荐阅读



最后推荐我一直在更新维护的项目和网站:

  • 最新的 AndroidX Jetpack 相关组件的实战项目 以及 原理分析的文章
    https://github.com/hi-dhl/AndroidX-Jetpack-Practice

  • LeetCode / 剑指 offer / 国内外大厂面试题 / 多线程 题解,语言 Java 和 kotlin,包含多种解法、解题思路、时间复杂度、空间复杂度分析

    剑指 offer:https://offer.hi-dhl.com
    LeetCode:https://leetcode.hi-dhl.com

  • 最新 Android 10 源码分析系列文章
    https://github.com/hi-dhl/Android10-Source-Analysis

  • 一系列国外的技术文章,每篇文章都会有译者思考部分,对原文的更加深入的分析
    https://github.com/hi-dhl/Technical-Article-Translation

  • 「为互联网人而设计,国内国外名站导航」涵括新闻、体育、生活、娱乐、设计、产品、运营、前端开发、Android 开发等等网址
    https://site.51git.cn



致力于分享一系列最新技术原创文章

长按二维码即可关注


我知道你在看

: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存