• Hilt详解


    Hilt 是什么

    Hilt 是 Android 的依赖项注入库,可减少在项目中执行手动依赖项注入的样板代码。Hilt 通过为项目中的每个 Android 类提供容器并自动管理其生命周期,提供了一种在应用中使用 DI(依赖项注入)的标准方法。Hilt 在 Dagger 的基础上构建而成,因而能够受益于 Dagger 的编译时正确性、运行时性能、可伸缩性。

    Hilt 基本使用

    1. 项目引入

    在project/build.gradle下加入hilt的插件

    dependencies {
        //hilt编译插件
        classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
    }
    
    • 1
    • 2
    • 3
    • 4

    在app/build.gradle下加入hilt依赖

    // Hilt dependencies
    implementation "com.google.dagger:hilt-android:$hilt_version"
    kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
    
    • 1
    • 2
    • 3

    2. Hilt基本使用

    Step0:准备一个待注入的依赖项

    class HelloPresenter @Inject constructor() {
        ...
    }
    
    • 1
    • 2
    • 3

    Step1:使用@HiltAndroidApp注解

    所有使用 Hilt 的应用都必须包含一个带有 @HiltAndroidApp 注解的 Application 类。@HiltAndroidApp 会触发 Hilt 的代码生成操作,生成的代码包括应用的一个基类,该基类充当应用级依赖项容器。

    @HiltAndroidApp
    class MyHiltApplication : Application() {}
    
    • 1
    • 2

    生成的这一 Hilt 组件会附加到 Application 对象的生命周期,并为其提供依赖项。此外,它也是应用的父组件,这意味着,其他组件可以访问它提供的依赖项。

    Step2:使用@AndroidEntryPoint将依赖注入Android类

    在 Application 类中设置了 Hilt 且有了应用级组件后,Hilt 可以为带有 @AndroidEntryPoint 注解的其他 Android 类提供依赖项:

    @AndroidEntryPoint
    class MainActivity : ComponentActivity() { ... }
    
    • 1
    • 2

    Step3:使用Hilt进行字段注入

    @AndroidEntryPoint 会为项目中的每个 Android 类生成一个单独的 Hilt 组件。这些组件可以从它们各自的父类接收依赖项。使用 @Inject 注解执行字段注入:

    @AndroidEntryPoint
    class MainActivity : ComponentActivity() {
        @Inject
        lateinit var presenter: HelloPresenter
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    3. Hilt 提供依赖项实例

    3.1 利用@Inject在构造器上提供实例

    如 2-Step0 所示,在类构造器上使用@Inject即可,不再赘述。

    3.2 使用 @Binds 提供接口实例

    某些情况下,依赖项不能通过构造函数提供实例,如:不能通过构造函数提供接口实例。这些情况下,可以使用 Hilt 模块向 Hilt 提供绑定信息。
    Hilt 模块是一个带有@Module注解的类。与 Dagger 模块一样,它会告知 Hilt 如何提供某些类型的实例。与 Dagger 模块不同的是,必须使用@InstallIn为 Hilt 模块添加注解,以告知 Hilt 每个模块将用在或安装在哪个 Android 类中。

    上面例子,假如HelloPresenter类的构造器中依赖HelloService接口:

    class HelloPresenter @Inject constructor(
        private val service: HelloService
    ) { 
        ...
    }
    
    interface HelloService {
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    HelloService是接口类,无法通过构造器提供实例。此时可通过带有@Module注解的类以及带有@Binds注解的方法,向 Hilt 提供实现类实例。

    interface HelloService {
        ...
    }
    
    class HelloServiceImpl @Inject constructor() : HelloService {
        override fun hello(name: String): String {
            ...
        }
    }
    
    @Module
    @InstallIn(ActivityComponent::class)
    abstract class HelloModule {
        /**
         * 提供 HelloService 实现类实例。参数为实现类,告知 Hilt 如何提供实现类的实例
         */
        @Binds
        abstract fun bindHelloService(service: HelloServiceImpl): HelloService
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    3.3 使用 @Provides 提供实例

    接口不是无法通过构造函数提供实例的唯一一种情况。如果某个类来自外部库(如 Retrofit、OkHttpClient 等类),或者必须使用构建器模式创建实例,也无法通过构造函数提供实例。
    接着前面的例子来讲。如果 HelloService 类的实例是使用 Retrofit 创建的,则需要告知 Hilt 如何提供此类型的实例,方法是在 Hilt 模块内创建一个函数,并使用 @Provides 为该函数添加注解。

    @Module
    @InstallIn(ActivityComponent::class)
    object HelloModule {
    
        @Provides
        fun provideHelloService(): HelloService {
            return Retrofit.Builder()
                .baseUrl("https://example.com")
                .build()
                .create(HelloService::class.java)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    4. 为同一类型提供多个绑定(限定符)

    我们知道,接口是可以有多个实现的。本例中当 HelloService 有多个实现类的时候,我们可能需要写多个 @Binds 或者 @Provides 修饰的方法,那么如何区分要使用哪个实现类呢?Hilt中提供了自定义的注解限定符。这些限定符可以作用在@Binds和@Provides修饰的方法上。比如下面定义两个限定符

    @Qualifier
    @Retention(AnnotationRetention.BINARY)
    annotation class HelloServiceForLocal
    
    @Qualifier
    @Retention(AnnotationRetention.BINARY)
    annotation class HelloServiceForRemote
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    然后在使用的地方进行标注,如下所示:

    @Module
    abstract class HelloModule {
        /**
         * 提供 HelloService 实现类实例。参数为实现类,告知 Hilt 如何提供实现类的实例
         */
        @HelloServiceForLocal
        @Binds
        abstract fun bindHelloService(service: HelloServiceImpl): HelloService
    
        @HelloServiceForRemote
        @Provides
        fun provideHelloService(): HelloService {
            return Retrofit.Builder()
                .baseUrl("https://example.com")
                .build()
                .create(HelloService::class.java)
        }
    }
    
    @AndroidEntryPoint
    class MainActivity : ComponentActivity() {
    
        @HelloServiceForLocal
        @Inject
        lateinit var localService: HelloService
    
        @HelloServiceForRemote
        @Inject
        lateinit var remoteService: HelloService
    
    	...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    代码中 localService 是由HelloModule#bindHelloService方法提供是实例,remoteService是由HelloModule#provideHelloService方法提供的实例。

    5. Hilt 中的预定义限定符

    Hilt 提供了一些预定义的限定符。例如,某些类可能需要来自应用或 activity 的 Context 类,因此 Hilt 提供了 @ApplicationContext 和 @ActivityContext 限定符。

    假设本例中的 HelloPresenter 类需要 activity 的上下文,则代码可以做如下修改:

    class HelloPresenter @Inject constructor(
        @ActivityContext context: Context
    ) { ... }
    
    • 1
    • 2
    • 3

    Hilt 为 Android 类生成的组件

    对于需要字段注入的每个 Android 类,都有一个关联的 Hilt 组件,可以在 @InstallIn 注解中引用该组件。每个 Hilt 组件负责将其绑定注入相应的 Android 类(如上述示例中使用的ActivityComponent)。

    Hilt 提供了以下组件:

    Hilt 组件注入器面向的对象
    SingletonComponentApplication
    ActivityRetainedComponent不适用
    ViewModelComponentViewModel
    ActivityComponentActivity
    FragmentComponentFragment
    ViewComponentView
    ViewWithFragmentComponent带有 @WithFragmentBindings 注解的 View
    ServiceComponentService

    ps:Hilt 不会为广播接收器生成组件,因为 Hilt 直接从 SingletonComponent 注入广播接收器。

    组件生命周期

    Hilt 会按照相应 Android 类的生命周期自动创建和销毁生成的组件类的实例。

    Hilt 生成的组件创建时机销毁时机
    SingletonComponentApplication#onCreate()Application 已销毁
    ActivityRetainedComponentActivity#onCreate()Activity#onDestroy()
    ViewModelComponentViewModel 已创建Activity#onDestroy()
    ActivityComponentActivity#onCreate()Activity#onDestroy()
    FragmentComponentFragment#onAttach()Fragment#onDestroy()
    ViewComponentView#super()View 已销毁
    ViewWithFragmentComponentView#super()View 已销毁
    ServiceComponentService#onCreate()Service#onDestroy()

    组件作用域

    默认情况下,Hilt 中的所有注入都未限定作用域。这意味着,每当应用请求注入依赖项时,Hilt 都会创建所需类型的一个新实例。

    在本例中,每当 Hilt 提供 HelloPresenter 作为其他类型的依赖项或通过字段注入提供它(如在 MainActivity 中)时,Hilt 都会提供 HelloPresenter 的一个新实例。

    Hilt 也允许将提供依赖项的作用域限定为特定组件。Hilt 只为作用域限定到的组件的每个实例创建一次限定作用域的注入,对该注入的所有请求共享同一实例。

    下表列出了生成的每个组件的作用域注解:

    Hilt 生成的组件Android 类作用域
    SingletonComponentApplication@Singleton
    ActivityRetainedComponentActivity@ActivityRetainedScoped
    ViewModelComponentViewModel@ViewModelScoped
    ActivityComponentActivity@ViewModelScoped
    FragmentComponentFragment@ViewModelScoped
    ViewComponentView@ViewModelScoped
    ViewWithFragmentComponent带有 @WithFragmentBindings 注解的 View@ViewScoped
    ServiceComponentService@ServiceScoped

    在本例中,如果您使用 @ActivityScoped 将 HelloPresenter 的作用域限定为 ActivityComponent,Hilt 会在相应 activity 的整个生命周期内提供 HelloPresenter 的同一实例。

    组件的层次结构

    将模块安装到组件后,其绑定就可以用作该组件中其他绑定的依赖项,也可以用作组件层次结构中该组件下的任何子组件中其他绑定的依赖项:
    在这里插入图片描述

    组件的默认绑定

    每个 Hilt 组件都附带一组默认绑定,Hilt 可以将其作为依赖项注入自定义绑定。这些绑定对应于常规 activity 和 fragment 类型,而不对应于任何特定子类。因为 Hilt 会使用单个 activity 组件定义来注入所有 activity。每个 activity 都有此组件的不同实例。

    Android 组件默认绑定
    SingletonComponentApplication
    ActivityRetainedComponentApplication
    ViewModelComponentSavedStateHandle
    ActivityComponentApplication 和 Activity
    FragmentComponentApplication、Activity 和 Fragment
    ViewComponentApplication、Activity 和 View
    ViewWithFragmentComponentApplication、Activity、Fragment 和 View
    ServiceComponentApplication 和 Service

    此外,还可以使用 @ApplicationContext 获得 Application 上下文绑定。例如:

    class HelloPresenter @Inject constructor(
        @ApplicationContext context: Context
    ) { ... }
    
    • 1
    • 2
    • 3

    可以使用 @ActivityContext 获得 Activity 上下文绑定。例如:

    class HelloPresenter @Inject constructor(
        @ActivityContext context: Context
    ) { ... }
    
    • 1
    • 2
    • 3

    Hilt 和 Dagger

    Hilt 在依赖项注入库 Dagger 的基础上构建而成,提供了一种将 Dagger 纳入 Android 应用的标准方法。
    关于 Dagger,Hilt 的目标如下:

    • 简化 Android 应用的 Dagger 相关基础架构。
    • 创建一组标准的组件和作用域,以简化设置、提高可读性以及在应用之间共享代码。
    • 提供一种简单的方法来为各种 build 类型(如测试、调试或发布)配置不同的绑定。

    由于 Android 操作系统会实例化它自己的许多框架类,因此在 Android 应用中使用 Dagger 要求您编写大量的样板。Hilt 可减少在 Android 应用中使用 Dagger 所涉及的样板代码。Hilt 会自动生成并提供以下各项:

    • 用于将 Android 框架类与 Dagger 集成的组件 - 您不必手动创建。简化了对于 Android 类,Dagger 需定义Component、Module、以及在 Android 类中调用DaggerHelloComponent.create().inject()的流程。
    • 作用域注解 - 与 Hilt 自动生成的组件一起使用。
    • 预定义的绑定 - 表示 Android 类,如 Application 或 Activity。
    • 预定义的限定符 - 表示 @ApplicationContext 和 @ActivityContext。

    Dagger 和 Hilt 代码可以共存于同一代码库中。不过,在大多数情况下,最好使用 Hilt 管理您在 Android 上对 Dagger 的所有使用。

  • 相关阅读:
    Systrace 线程 CPU 运行状态分析技巧 - Running 篇
    用了那么久的 Lombok,你知道它的原理么?
    java-net-php-python-jsp班导师助理业务管理信息系统计算机毕业设计程序
    c++ 动态分配内存
    cesium画的矩形不是方的?
    Kendo UI Grid 批量编辑使用总结
    看完这篇,医学小白也能轻松玩转文献查阅
    MIT6.830-2022-lab1实验思路详细讲解
    SpringBoot/Spring AOP默认动态代理方式
    java计算机毕业设计鞋店销售管理源程序+mysql+系统+lw文档+远程调试
  • 原文地址:https://blog.csdn.net/cspecialy/article/details/133970824