Android开发Android技术知识Android Jetpack

Android核心技术——Jetpack Hilt依赖注入

2022-12-28  本文已影响0人  谁动了我的代码

依赖注入是什么

个人理解:把有依赖关系的类放在容器中,解析这些类的实例,并在运行时注入到对应的字段中,就是依赖注入,目的是为了类的解耦

例子:A 类 中用到了 B 类,一般情况下需要在 A 类中 new B() 的实例对象

采用依赖注入后,在 A 类中 定义一个私有的 B 类 字段。并在运行的时候通过从相关的容器中获取出来 B 的对象并注入到 A 类中的 字段中。

这样做的好处是什么?

如果有很多个类需要使用 B 类。难道都要在各自的类中进行 new B() 吗。这样对后期的维护和管理都是不方便的。使用 依赖注入则就变得很简单了。

HILT 是什么

Hilt 是 Android 的依赖注入库,其实是基于 Dagger 。可以说 Hilt 是专门为 Andorid 打造的。

Hilt 创建了一组标准的 组件和作用域。这些组件会自动集成到 Android 程序中的生命周期中。在使用的时候可以指定使用的范围,事情作用在对应的生命周期当中。

HILT 常用的注解的含义

Hilt 支持最常见的 Android 类 Application、Activity、Fragment、View、Service、BroadcastReceiver 等等,但是您可能需要在Hilt 不支持的类中执行依赖注入,在这种情况下可以使用 @EntryPoint 注解进行创建,Hilt 会提供相应的依赖。

HILT 中的组件(COMPENENT)

使用 @Module 注解的类,需要使用 @Installin 注解来指定 module 的范围。

例如 @InstallIn(ApplicationComponent::class) 注解的 Module 就会绑定到 Application 的生命周期上。

Hilt 提供了以下组件来绑定依赖与对应 Android 类的活动范围

Hilt 组件 对应 Android 类活动的范围
ApplicationComponent Application
ActivityRetainedComponent ViewModel
ActivityComponent Activity
FragmentComponent Fragment
ViewComponent View
ViewWithFragmentComponent View annotated with @WithFragmentBindings
ServiceComponent Service

Hilt 没有为 broadcast receivers 提供组件,因为 Hilt 直接进从 ApplicationComponent 中注入 broadcast receivers。

HILT 中组件的生命周期

Hilt 会根据相应的 Android 类生命周期自动创建和销毁组件的实例,对应关系如下:

Hilt 提供的组件 创建对应的生命周期 结束对应的生命周期 作用范围
ApplicationComponent Application#onCreate() Application#onDestroy() @Singleton
ActivityRetainedComponent Activity#onCreate() Activity#onDestroy() @ActivityRetainedScope
ActivityComponent Activity#onCreate() Activity#onDestroy() @ActivityScoped
FragmentComponent Fragment#onAttach() Fragment#onDestroy() @FragmentScoped
ViewComponent View#super() View destroyed @ViewScoped
ViewWithFragmentComponent View#super() View destroyed @ViewScoped
ServiceComponent Service#onCreate() View destroyed @ViewScoped

Android常用的依赖注入框架

Dagger

由Square公司开源,基于Java反射去实现的,从而有两个潜在的隐患:

Dagger2

由 Google 开发,基于 Java 注解实现的,把 Dagger1 反射的那些弊端解决了:

通过注解,Dagger2 会在编译时期自动生成用于依赖注入的代码,不会增加任何运行耗时。另外,Dagger2 会在编译时检查依赖注入用法是否正确,若不正确则会直接编译失败,从而将问题尽可能早地抛出。即项目正常编译通过,说明依赖注入的用法基本没问题了。

但 Dagger2 使用比较复杂,若不能很好地使用它,可能会拖累你的项目,甚至会将一些简单的项目过度设计。

Hilt

Google 发布了 Hilt,是在依赖项注入库 Dagger 的基础上构建而成,一个专门面向 Android 的依赖注入框架。

相比于 Dagger2,Hilt 最明显的特征就是: 简单、提供了 Android 专属的 API。

Hilt的使用

1、依赖版本配置

项目根build.gradle依赖

ext.kotlin_version = "1.5.0"//kotlin版本
ext.hilt_version = '2.35'//hilt版本
repositories {
    google()
    mavenCentral()
}
dependencies {
    classpath "com.android.tools.build:gradle:4.2.1"
    classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    classpath 'com.google.dagger:hilt-android-gradle-plugin:2.35'
}

项目app中build.gradle依赖

implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"

项目模块中build.gradle所使用插件

id 'kotlin-kapt'
id 'kotlin-android-extensions'

2、Hilt应用

1.定义Hilt应用类

@HiltAndroidApp
class MyApplication : MultiDexApplication() {

}

然后,在AndroidManifest.xml中引入我们自定义的Application类。此时,生成的Hilt组件会附加到 Application 对象的生命周期,并为其提供依赖项。此外,由于它也是应用的父组件,所以其他组件可以访问它提供的依赖项。

2.创建注入类

为了执行字段注入,需要在类的构造函数中使用 @Inject 注解,以告知 Hilt 如何提供该类的实例

class Car @Inject constructor() {
    var name: String = ""

    fun run() {
    }
}

使用 @Inject 注释执行字段 @AndroidEntryPoint 会为项目中的每个 Android 类生成一个单独的 Hilt 组件。这些组件可以从它们各自的父类接收依赖项, 如需从组件获取依赖项,请使用 @Inject 注释执行字段注入, 注意:Hilt注入的字段是不可以声明成private的;

3.依赖项注入 Android 类

用@AndroidEntryPoint注释类 目前支持6类入口点:Application(通过使用 @HiltAndroidApp),Activity,Fragment,View,Service,BroadcastReceiver

使用 @AndroidEntryPoint 注解 Android 类,还必须为依赖于该类的 Android 类添加注释,例如为注解 fragment ,则还必须为该 fragment 依赖的 Activity 添加@AndroidEntryPoint注释。

@AndroidEntryPoint
class HiltActivity : AppCompatActivity() {
    @Inject
    lateinit var car: Car
}

注意:带参数的依赖注入,需要构造函数中所依赖的所有其他对象都支持依赖注入;例如:Car的构造函数有Engine,则Engine构造函数也需要@Inject。

3、Hilt模块

有时一些类型参数(如接口或来自外部库的类)不能通过构造函数注入,对于这种情况,我们可以使用Hilt 模块来向Hilt提供绑定信息。

Hilt模块是一个带有@Module注释的类,并使用 @InstallIn 设置作用域。与Dagger模块的作用一样,它会告知Hilt如何提供某些类型的实例。与Dagger模块不同的是,我们必须使用@InstallIn注解为Hilt模块添加注释,以告知Hilt模块将用在哪个Android类中。

1.@Binds注入接口

由于Phone是一个接口,无法通过构造函数注入Hilt,而应向Hilt提供绑定信息。即在Hilt模块内创建一个带有@Binds注释的抽象函数。

通常,带有注释的函数会向Hilt提供以下信息:

  1. 函数返回类型会告知Hilt函数提供的是哪个接口的实例。
  2. 函数参数会告知Hilt需要提供哪种实现。
//1. 接口
interface Phone {
    fun call()
}
//2. 实现类
class Huawei @Inject constructor() : Phone {
    override fun call() {
    }
}
//3. 被注入的类,入参是接口类型
class People @Inject constructor(val phone: Phone) {
    fun call() {
        phone.call()
    }
}
//4. 使用@Binds注入接口实例
@Module
@InstallIn(ActivityComponent::class)
abstract class PhoneModel {
    @Binds
    abstract fun bindPhone(phone: Huawei): Phone
}
//5. 使用注入的实例
@AndroidEntryPoint
class HiltActivity : AppCompatActivity() {
    @Inject
    lateinit var people: People

    fun test() {
        people.phone.call()
    }
}

由于PhoneModel 带有 @InstallIn(ActivityComponent.class) 注释,因为我们可以将该依赖项注入Activity中。并且,PhoneModel中的所有依赖项都可以在所有Activity中使用。

2.@Provides注入实例

如果某个类不归您所有(因为它来自外部库,如 Retrofit、OkHttpClient 或 Room 数据库等类),或者必须使用构建器模式创建实例,也无法通过构造函数注入。

这种情况注入方法是在Hilt模块内创建一个函数,然后使用@Provides为该函数添加注释。

带有注释的函数会向 Hilt 提供以下信息:

  1. 函数返回类型会告知 Hilt 函数提供哪个类型的实例。
  2. 函数参数会告知 Hilt 相应类型的依赖项。
  3. 函数主体会告知 Hilt 如何提供相应类型的实例。每当需要提供该类型的实例时,Hilt 都会执行函数主体。
@Module
@InstallIn(SingletonComponent::class)
class DINetworkModule {

    /**
     * [OkHttpClient]依赖提供方法
     *
     * @return OkHttpClient
     */
    @Singleton
    @Provides
    fun provideOkHttpClient(): OkHttpClient {
        // 日志拦截器部分
        val level = if (BuildConfig.VERSION_TYPE != VersionStatus.RELEASE) BODY else NONE
        val logInterceptor = HttpLoggingInterceptor().setLevel(level)

        return OkHttpClient.Builder()
            .retryOnConnectionFailure(true)
            .connectTimeout(15L * 1000L, TimeUnit.MILLISECONDS)
            .readTimeout(20L * 1000L, TimeUnit.MILLISECONDS)
            .writeTimeout(20L * 1000L, TimeUnit.MILLISECONDS)
            .addInterceptor(logInterceptor)
//            .addInterceptor(CookiesInterceptor())
            .build()
    }

    /**
     * 项目主要服务器地址的[Retrofit]依赖提供方法
     *
     * @param okHttpClient OkHttpClient OkHttp客户端
     * @return Retrofit
     */
    @Singleton
    @Provides
    fun provideMainRetrofit(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .baseUrl(HttpBaseUrlConstant.BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .client(okHttpClient)
            .build()
    }
}

3.限定符

3.1为同一类型提供多个绑定

如果需要让Hilt以依赖项的形式提供同一类型的不同实现,那么必须向 Hilt 提供多个绑定,同一类型定义多个绑定可以使用限定符来实现。

//1. 接口和实现类
interface Phone {
    fun call()
}

class Huawei @Inject constructor() : Phone {
    override fun call() {
    }
}

class Xiaomi @Inject constructor() : Phone {
    override fun call() {
    }
}

//2. 创建多个类型的注解
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BindHuawei

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BindXiaomi

//@Retention:注解的生命周期
//AnnotationRetention.SOURCE:仅编译期,不存储在二进制输出中
//AnnotationRetention.BINARY:存储在二进制输出中,但对反射不可见
//AnnotationRetention.RUNTIME:存储在二进制输出中,对反射可见


//3. 在Hilt模块中使用注解
@Module
@InstallIn(ActivityComponent::class)
abstract class PhoneModel {
    @BindHuawei
    @Binds
    abstract fun bindHuawei(cpu: Huawei): Phone

    @ BindXiaomi
    @Binds
    abstract fun bindXiaomi(cpu: Xiaomi): Phone
}

//4. 使用依赖注入获取实例,可以用在字段注解,也可以用在构造函数或者方法入参中
@AndroidEntryPoint
class HiltActivity : AppCompatActivity() {
    @BindHuawei
    @Inject
    lateinit var huawei: Phone

    @BindXiaomi
    @Inject
    lateinit var xiaomi: Phone

    fun use() {
        huawei.call()
        xiaomi.call()
    }
}

如果需要向某个类型添加限定符,那么应该向提供该依赖项的所有可能的渠道都添加限定符。这是因为让基本的实现或通用的实现在不带限定符的情况下避免出错,也是为了避免导致Hilt注入错误的依赖项。

3.2Hilt中的预定义限定符

Hilt提供了一些预定义的限定符。 Hilt 提供的@ApplicationContext 和 @ActivityContext 限定符用来获取Context 类

class ModuleOne @Inject constructor(@ApplicationContext private val context: Context)

class ModuleTwo @Inject constructor(@ActivityContext private val context: Context)

对于Application和Activity这两个类型,Hilt也是给它们预置好了注入功能(必须是这两个,即使子类也不可以)

class ModuleOne @Inject constructor(val application: Application)

class ModuleTwo @Inject constructor(val activity: Activity)
image

以上是Android开发中的Jetpack Hilt依赖注入学习,在Android开发进阶中还有大量的核心技术《Android核心技术手册》全部整理在这份文档中。上千个知识点30多个板块笔记,需要的可以参考学习进阶。

文末

HILT 好处

上一篇 下一篇

猜你喜欢

热点阅读