JetPack之Hilt使用及探究

什么是IOC

IOC是Inversion of Control的缩写,翻译为控制反转,是面向对象编程中的一种设计原则,可以用来降低代码之间的耦合度。

因此IOC中最常见的方式叫做依赖注入(Dependency Injection,简称DI),所谓依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。

类里面声明的变量叫做依赖,通常依赖需要自己创建对象,自己进行管理。依赖注入,就是IOC容器在运行期间,让外部帮你初始化你的依赖,然后注入到你声明的变量上。

让外部初始就算依赖注入,那么工厂模式,Builder模式也是依赖注入。那么Hilt就是使用了注解的方式,让依赖注入变得更方便。

为什么要用依赖注入

如果一个对象需要被共享,或者可能在多个类中被使用,为了解耦,你就可以使用以来注入来初始化它。

Hilt 是什么

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

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

Hilt支持的Android组件
引入 Hilt
  • 在项目的build.gradle中添加引用
buildscript {
    dependencies {
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.42'
    }
}
  • 在App的build.gradle中添加plugin的引用和依赖:
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'

dependencies {
    implementation "com.google.dagger:hilt-android:2.42"
    kapt "com.google.dagger:hilt-android-compiler:2.42"
}
常用注解

Hilt常用注解主要是:@HiltAndroidApp、@AndroidEntryPoint、@Inject、@Module、@InstallIn、@Binds、@Provides、@EntryPoint 等等。

  • Hlit初始化
    所有使用Hilt的项目都需要包含一个带有@HiltAndroidApp注释的Application类.

@HiltAndroidApp会触发Hilt代码生成,生成的代码包含应用的一个基类,该基类充当应用级依赖项容器。

@HiltAndroidApp
class MyApplication: Application() {

    override fun onCreate() {
        super.onCreate()
        
    }
}

只有 Application 这个入口点是使用 @HiltAndroidApp 注解来声明的。其他的所有入口点,都是用 @AndroidEntryPoint 注解来声明的。

Hilt为何要增加@HiltAndroidApp注解

1:将ApplicationContextModule添加至应用组件中,获取应用组件applicationContext
2:自动实现了依赖注入,免去了类似Dagger的手动调用

将依赖项注入Android类 @AndroidEntryPoint

Hilt目前支持以下Android类:

Application(通过使用 @HiltAndroidApp)
Activity
Fragment
View
Service
BroadcastReceiver

为什么需要在使用依赖注入的组件上添加AndroidEntryPoint注解?因为Hilt需要找到使用了该注解的类,自动找到合适的位置,比如反射activity的oncreate方法中,进行初始化方法调用。

@Inject

如需从组件获取依赖项,可以使用@Inject注释执行字段注入。

class Truck @Inject constructor(val driver: Driver) {

    fun deliver() {
        Log.i("minfo", "Truck is delivering cargo driver by $driver")
    }
}

class Driver @Inject constructor() {

}

MainActivity中使用注入truck对象,并调用deliver()方法。

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var truck: Truck

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        truck.deliver()
    }
}
Hilt模块 @Module

有时,类型不能通过构造函数注入。例如,您不能通过构造函数注入接口。此外,您也不能通过构造函数注入不归您所有的类型,如来自外部库的类。在这些情况下,您可以使用Hilt @Module向Hilt提供绑定信息。比如我们常用于创建依赖类的对象(例如第三方库OkHttp、Retrofit等等),使用@Module注解的类,需要使用@InstallIn注解指定module的范围。

Hilt内置组件和组件作用域

InstallIn,就是安装到的意思。那么@InstallIn(ActivityComponent::class),就是把这个模块安装到Activity组件当中。
Hilt一共内置了7种组件类型,分别用于注入到不同的场景,如下表所示。

@Singleton

Hilt会为每次的依赖注入行为都创建不同的实例。这种默认行为在很多时候确实是非常不合理的,比如我们提供的Retrofit和OkHttpClient的实例,理论上它们全局只需要一份就可以了,每次都创建不同的实例明显是一种不必要的浪费。

如后面写到的NetworkModule内部创建OkHttpClient、Retrofit使用了注解@Singleton。

@Module
@InstallIn(SingletonComponent::class)
// 把NetworkModule绑定到HiltApp 的生命周期。
object NetworkModule {
}
@Binds 注入接口实例

接口是无法通过构造方法进行注入的,需要通过在Hilt 模块内创建一个带有@Binds注释的抽象函数进行注入。@Binds 注释会告知Hilt在需要提供接口的实例时要使用哪种实现。

示例:

//需要注入接口
interface Engine {
    fun start()
    fun shutdown()
}

//接口实现类
class GasEngine @Inject constructor() : Engine {

    override fun start() {
        Log.i("minfo", "GasEngine start")
    }

    override fun shutdown() {
        Log.i("minfo", "GasEngine shotdown")
    }

}

@Module
@InstallIn(ActivityComponent::class)
abstract class EnginModule {

    @Binds
    abstract fun bindEngin(gasEngine: GasEngine): Engine   //指定子类和实现类
    //@Binds 注释会告知Hilt在需要提供接口的实例时要使用哪种实现。
    //@Binds告知 需要提供 Engin接口的实例是有该抽象方法的参数类型对象提供,所以GasEngine需要实现Engine接口
}

在Truck类中注入engine接口:

class Truck @Inject constructor(val driver: Driver) {
    @Inject
    lateinit var engine: Engine

    fun deliver() {
        engine.start()
        Log.i("minfo", "Truck is delivering cargo driver by $driver")
        engine.shutdown()
    }
}
@Provides 注入实例

第三方库如:Retrofit、OkHttpClient 或 Room数据库等都是无法通过构造函数注入,我们去改不了三方库的代码,在里面加入@Inject,这时就需要使用@Provides注入实例,在Provides注解的方法中,给出具体的实现代码。

@Module
@InstallIn(SingletonComponent::class)
// 把NetworkModule绑定到HiltApp 的生命周期。
object NetworkModule {

    @Provides
    @Singleton
    fun getOkhttpClient() : OkHttpClient {
        return OkHttpClient.Builder().build()
    }

    @Provides
    @Singleton
    fun createRetrofit(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .client(okHttpClient)
            .baseUrl("https://www.wanandroid.com")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }

    @Provides
    @Singleton
    fun getService(): ApiService {
        return createRetrofit(getOkhttpClient()).create(ApiService::class.java)
    }
    
}

如上方式,类上添加@InstallIn(SingletonComponent::class) 以及提供对象添加@Singleton注解,就是提供单例依赖注入的写法。如果需要每次使用初始化,就去掉这两个注解。

其他生命周期,@InstallIn可将注解改为:ActivityComponent/FragmentComponent/ViewComponent/ActivityRetainedComponent的生命周期。

使用注入对象

直接在activity中进行注入:

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var truck: Truck

    @Inject
    lateinit var apiService: ApiService

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        truck.deliver()
    }
}
Hilt 中的预定义限定符

如果有个我们想要依赖注入的类,它又是依赖于Context的,这个情况要如何解决呢?

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

@Singleton
class Driver @Inject constructor(val context: Context) {
}

现在你编译一下项目一定会报错,原因也很简单,Driver类无法被依赖注入了,因为Hilt不知道要如何提供Context这个参数。其实只需要在Context参数前加上一个@ApplicationContext注解,代码就能编译通过了。

@Singleton
class Driver @Inject constructor(@ApplicationContext val context: Context) {
}

@Singleton
class Driver @Inject constructor(@ActivityContext val context: Context) {
}
Hilt在ViewModel的使用
@ActivityRetainedScoped
class MyViewModel @Inject constructor() : ViewModel() {

    fun loadData() {
        viewModelScope.launch {

        }
    }
}

在activity中使用viewmodel

    @Inject
    lateinit var viewModel: MyViewModel
@EntryPoint注解

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

原理篇

@HiltAndroidApp注解做了什么事

首先会创建一个包含很多接口和抽象类的 java 文件 MyApplication_HiltComponents,用来规范好所有的类关系和行为。

然后会为 Application 创建一个注射器 MyApplication_GeneratedInjector,负责将这个 Application 注入。

然后再创建一个 Application 的父类 Hilt_Application,用来替换掉源代码的继承关系,以此实现将 Hilt 插入到正常代码逻辑中,实现 Hilt 的功能。原有application对象的父类就变成了Hilt_Application。

@AndroidEntryPoint注解了MainActivity之后Hilt帮我们做了什么?

1.Hilt自动帮我们生成了一个继承自AppCompatActivity的名称为Hilt_MainActivity.java类。
然后让原本的Mainactivity继承于Hilt_MainActivity,Hilt_MainActivity会在onCreate方法中调用自己的inject方法进行注入,将声明依赖进行赋值。

Hilt_MainActivity类中:

protected void onCreate(@Nullable Bundle savedInstanceState) {
  inject();
  super.onCreate(savedInstanceState);
}
  1. 对使用了@Inject注解的依赖,生成了XXX_Factory类,类中是对该对象的创建方法。

参考:
https://juejin.cn/post/7111986248603926541
https://blog.csdn.net/guolin_blog/article/details/109787732
https://www.bilibili.com/video/BV1nK4y1v7u9/
https://blog.csdn.net/xx23x/article/details/121636223

Github参考demo:
https://github.com/running-libo/HiltUse/tree/master
https://github.com/running-libo/MviFlowHilt

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,302评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,563评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,433评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,628评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,467评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,354评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,777评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,419评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,725评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,768评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,543评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,387评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,794评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,032评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,305评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,741评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,946评论 2 336

推荐阅读更多精彩内容