ToB企服应用市场:ToB评测及商务社交产业平台

标题: Android使用Hilt依靠注入,让人看不懂你代码 [打印本页]

作者: 熊熊出没    时间: 2024-7-29 20:51
标题: Android使用Hilt依靠注入,让人看不懂你代码
前言

之前接手的一个项目里有些代码看得云里雾里的,找了半天没有找到对象创建的地方,厥后才发现原来使用了Hilt进行了依靠注入。Hilt相比Dagger虽然已经比较简洁,但对初学者来说照旧有些门槛,并且网上的许多文章都是搬自官网,入手容易深入难,假如你对Hilt不相识或是想相识得更多,那么接下来的内容将助力你玩转Hilt。
通过本篇文章,你将相识到:
     1. 什么是依靠注入?

什么是依靠?

以手机为例,要组装一台手机,我们必要哪些部件呢?
从宏观上分类:软件+硬件。
由此我们可以说:手机依靠了软件和硬件。
而反映到代码的天下:
  1. class FishPhone(){
  2.     val software = Software()
  3.     val hardware = Hardware()
  4.     fun call() {
  5.         //打电话
  6.         software.handle()
  7.         hardware.handle()
  8.     }
  9. }
  10. //软件
  11. class Software() {
  12.     fun handle(){}
  13. }
  14. //硬件
  15. class Hardware() {
  16.     fun handle(){}
  17. }
复制代码
FishPhone 依靠了两个对象:分别是Software和Hardware。
Software和Hardware是FishPhone的依靠(项)。
什么是注入?

上面的Demo,FishPhone内部自主构造了依靠项的实例,考虑到依靠的变革挺大的,每次依靠项的改变都要改动到FishPhone,容易堕落,也不是那么灵活,因此考虑从外部将依靠传进来,这种方式称之为:依靠注入(Dependency Injection 简称DI)
有几种方式:
     构造函数依靠注入:
  1. class FishPhone(val software: Software, val hardware: Hardware){
  2.     fun call() {
  3.         //打电话
  4.         software.handle()
  5.         hardware.handle()
  6.     }
  7. }
复制代码
FishPhone的功能比较纯粹就是打电话功能,而依靠项都是外部传入提拔了灵活性。
为什么必要依靠注入框架?

手机制造出来后交给客户使用。
  1. class Customer() {
  2.     fun usePhone() {
  3.         val software = Software()
  4.         val hardware = Hardware()
  5.         FishPhone(software, hardware).call()
  6.     }
  7. }
复制代码
用户想使用手机打电话,还得本身创建软件和硬件,这个手机还能卖出去吗?
而不想创建软件和硬件那得让FishPhone本身负责去创建,那不是又回到上面的场景了吗?
你大概会说:FishPhone内部就依靠了两个对象而已,本身负责创建又怎么了?
解耦

再看看如下Demo:
  1. interface ISoftware {
  2.     fun handle()
  3. }
  4. //硬件
  5. interface IHardware {
  6.     fun handle()
  7. }
  8. //软件
  9. class SoftwareImpl() : ISoftware {
  10.     override fun handle() {}
  11. }
  12. //硬件
  13. class HardwareImpl : IHardware {
  14.     override fun handle() {}
  15. }
  16. class FishPhone() {
  17.     val software: ISoftware = SoftwareImpl()
  18.     val hardware: IHardware = HardwareImpl()
  19.     fun call() {
  20.         //打电话
  21.         software.handle()
  22.         hardware.handle()
  23.     }
  24. }
复制代码
FishPhone 只关注软件和硬件的接口,至于具体怎么实现它不关心,这就达到相识耦的目的。
既然要解耦,那么SoftwareImpl()、HardwareImpl()就不能出现在FishPhone里。
应该改为如下形式:
  1. class FishPhone(val software: ISoftware, val hardware: IHardware) {
  2.     fun call() {
  3.         //打电话
  4.         software.handle()
  5.         hardware.handle()
  6.     }
  7. }
复制代码
消除模板代码

即使我们不考虑解耦,假若HardwareImpl里又依靠了cpu、gpu、disk等模块:
  1. //硬件
  2. class HardwareImpl : IHardware {
  3.     val cpu = CPU(Regisgter(), Cal(), Bus())
  4.     val gpu = GPU(Image(), Video())
  5.     val disk = Disk(Block(), Flash())
  6.     //...其它模块
  7.     override fun handle() {}
  8. }
复制代码
现在仅仅只是三个模块,如果依靠更多的模块大概模块的本身也必要依靠其它子模块,比如CPU必要依靠寄存器、运算单元等等,那么我们就必要写更多的模板代码,要是我们只必要声明一下想要使用的对象而不用管它的创建就好了。
  1. class HardwareImpl(val cpu: CPU, val gpu: GPU, val disk: Disk) : IHardware {
  2.     override fun handle() {}
  3. }
复制代码
可以看出,下面的代码比上面的简洁多了。
     先想想如果我们想要实现如许的框架必要怎么做呢?
信赖许多小伙伴最质朴的想法就是:使用工厂模式,你传参告诉我想要什么对象我给你构造出来。
这个想法是半主动注入,由于我们还要调用工厂方法去获取,而全主动的注入通常来说是使用注解标注实现的。
2. Hilt 的引入与根本使用

Hilt的引入

从Dagger到Dagger2再到Hilt(Android专用),设置越来越简朴也比较容易上手。
前面说了依靠注入框架的必要性,我们就想迫不及待的上手,但难度可想而知,还好大神们早就造好了轮子。
以AGP 7.0 以上为例,来看看Hilt框架是如何引入的。
一:project级别的build.gradle 引入如下代码:
  1. plugins {
  2.     //指定插件地址和版本
  3.     id 'com.google.dagger.hilt.android' version '2.48.1' apply false
  4. }
复制代码
二:module级别的build.gradle引入如下代码:
  1. plugins {
  2.     id 'com.android.application'
  3.     id 'org.jetbrains.kotlin.android'
  4.     //使用插件
  5.     id 'com.google.dagger.hilt.android'
  6.     //kapt生成代码
  7.     id 'kotlin-kapt'
  8. }
  9. //引入库
  10. implementation 'com.google.dagger:hilt-android:2.48.1'
  11. kapt 'com.google.dagger:hilt-compiler:2.48.1'
复制代码
实时更新最新版本以及AGP7.0以下的引用请参考:Hilt最新版本设置
Hilt的简朴使用

前置步骤整好了接下来看看如何使用。
一:表明该App可以使用Hilt来进行依靠注入,添加如下代码:
  1. @HiltAndroidApp
  2. class MyApp : Application() {
  3.     override fun onCreate() {
  4.         super.onCreate()
  5.     }
  6. }
复制代码
@HiltAndroidApp 添加到App的入口,即表现依靠注入的环境已经搭建好。
二:注入一个对象到MyApp里:
有个类定义如下:
  1. class Software {
  2.     val name = "fish"
  3. }
复制代码
我们不想显示的构造它,想借助Hilt注入它,那得先告诉Hilt这个类你帮我注入一下,改为如下代码:
  1. class Software @Inject constructor() {
  2.     val name = "fish"
  3. }
复制代码
在构造函数前添加了@Inject注解,表现该类可以被注入。
而在MyApp里使用Software对象:
  1. @HiltAndroidApp
  2. class MyApp : Application() {
  3.     @Inject
  4.     lateinit var software: Software
  5.    
  6.     override fun onCreate() {
  7.         super.onCreate()
  8.         println("inject result:${software.name}")
  9.     }
  10. }
复制代码
对引用的对象使用@Inject注解,表现期望Hilt帮我将这个对象new出来。
最后查看打印输出正确,说明Software对象被创建了。
这是最简朴的Hilt应用,可以看出:
     如何注入接口?

一:错误示范
上面提到过,使用DI的利益之一就是解耦,而我们上面注入的是类,现在我们将Software抽象为接口,很容易就会想到如下写法:
  1. interface ISoftware {
  2.     fun printName()
  3. }
  4. class SoftwareImpl @Inject constructor(): ISoftware{
  5.     override fun printName() {
  6.         println("name is fish")
  7.     }
  8. }
  9. @HiltAndroidApp
  10. class MyApp : Application() {
  11.     @Inject
  12.     lateinit var software: ISoftware
  13.     override fun onCreate() {
  14.         super.onCreate()
  15.         println("inject result:${software.printName()}")
  16.     }
  17. }
复制代码
不幸的是上述代码编译失败,Hilt提示说不能对接口使用注解,由于我们并没有告诉Hilt是谁实现了ISoftware,而接口本身不能直接实例化,因此我们必要为它指定具体的实现类。
二:正确示范
再定义一个类如下:
  1. @Module
  2. @InstallIn(SingletonComponent::class)
  3. abstract class SoftwareModule {
  4.     @Binds
  5.     abstract fun bindSoftware(impl: SoftwareImpl):ISoftware
  6. }
复制代码
    如此一来我们就告诉了Hilt,SoftwareImpl是ISoftware的实现类,于是Hilt注入ISoftware对象的时候就知道使用SoftwareImpl进行实例化。
其它稳定运行一下:

可以看出,现实注入的是SoftwareImpl。
   @Binds 适用在我们可以或许修改类的构造函数的场景
  如何注入第三方类

上面的SoftwareImpl是我们可以修改的,由于使用了@Inject修饰其构造函数,所以可以在其它地方注入它。
在一些时候我们不想使用@Inject修饰大概说这个类我们不能修改,那该如何注入它们呢?
一:定义Provides模块
  1. @Module
  2. @InstallIn(SingletonComponent::class)
  3. object HardwareModule {
  4.     @Provides
  5.     fun provideHardware():Hardware {
  6.         return Hardware()
  7.     }
  8. }
复制代码
    二:依靠使用
而Hardware定义如下:
  1. class Hardware {
  2.     fun printName() {
  3.         println("I'm fish")
  4.     }
  5. }
复制代码
在MyApp里引用Hardware:

虽然Hardware构造函数没有使用@Inject注解,但是我们依然可以或许使用依靠注入。
固然我们也可以注入接口:
  1. interface IHardware {
  2.     fun printName()
  3. }
  4. class HardwareImpl : IHardware {
  5.     override fun printName() {
  6.         println("name is fish")
  7.     }
  8. }
复制代码
想要注入IHardware接口,必要定义provides模块:
  1. @Module
  2. @InstallIn(SingletonComponent::class)
  3. object HardwareModule {
  4.     @Provides
  5.     fun provideHardware():IHardware {
  6.         return HardwareImpl()
  7.     }
  8. }
复制代码
  @Provides适用于无法修改类的构造函数的场景,多用于注入第三方的对象
  3. Hilt 的进阶使用

限定符

上述 ISoftware的实现类只有一个,假设现在有两个实现类呢?
比如说这些软件可以是美国提供,也可以是中国提供的,依据上面的经验我们很容易写出如下代码:
  1. class SoftwareChina @Inject constructor() : ISoftware {
  2.     override fun printName() {
  3.         println("from china")
  4.     }
  5. }
  6. class SoftwareUS @Inject constructor() : ISoftware {
  7.     override fun printName() {
  8.         println("from US")
  9.     }
  10. }
  11. @Module
  12. @InstallIn(SingletonComponent::class)
  13. abstract class SoftwareModule {
  14.     @Binds
  15.     abstract fun bindSoftwareCh(impl: SoftwareChina):ISoftware
  16.     @Binds
  17.     abstract fun bindSoftwareUs(impl: SoftwareUS):ISoftware
  18. }
  19. //依赖注入:
  20. @Inject
  21. lateinit var software: ISoftware
复制代码
欢欣鼓舞的进行编译,然而却报错:

也就是说Hilt想要注入ISoftware,但不知道选择哪个实现类,SoftwareChina照旧SoftwareUS?没人告诉它,所以它渺茫了,索性都绑定了。
这个时候我们必要借助注解:@Qualifier 限定符注解来对实现类进行限制。
改造一下:
  1. @Module
  2. @InstallIn(SingletonComponent::class)
  3. abstract class SoftwareModule {
  4.     @Binds
  5.     @China
  6.     abstract fun bindSoftwareCh(impl: SoftwareChina):ISoftware
  7.     @Binds
  8.     @US
  9.     abstract fun bindSoftwareUs(impl: SoftwareUS):ISoftware
  10. }
  11. @Qualifier
  12. @Retention(AnnotationRetention.BINARY)
  13. annotation class US
  14. @Qualifier
  15. @Retention(AnnotationRetention.BINARY)
  16. annotation class China
复制代码
定义新的注解类,使用@Qualifier修饰。
而后在Module里,分别使用注解类修饰返回的函数,如bindSoftwareCh函数指定返回SoftwareChina来实现ISoftware接口。
最后在引用依靠注入的地方分别使用@China @US修饰。
  1.     @Inject
  2.     @US
  3.     lateinit var software1: ISoftware
  4.     @Inject
  5.     @China
  6.     lateinit var software2: ISoftware
复制代码
此时,虽然software1、software2都是ISoftware范例,但是由于我们指定了限定符@US、@China,因此最后真正的实现类分别是SoftwareChina、SoftwareUS。
   @Qualifier 重要用在接口有多个实现类(抽象类有多个子类)的注入场景
  预定义限定符

上面提及的限定符我们还可以扩展其使用方式。
你大概发现了,上述提及的可注入的类构造函数都是无参的,许多时候我们的构造函数是必要有参数的,比如:
  1. class Software @Inject constructor(val context: Context) {
  2.     val name = "fish"
  3.     fun getWindowService(): WindowManager?{
  4.         return context.getSystemService(Context.WINDOW_SERVICE) as? WindowManager
  5.     }
  6. }
  7. //注入
  8. @Inject
  9. lateinit var software: Software
复制代码
这个时候编译会报错:

意思是Software依靠的Context没有进行注入,因此我们必要给它注入一个Context。
由上面的分析可知,Context类不是我们可以修改的,只能通过@Provides方式提供其注入实例,并且Context有许多子类,我们必要使用@Qualifier指定具体实现类,因此很容易我们就想到如下对策。
先定义Module:
  1. @Module
  2. @InstallIn(SingletonComponent::class)
  3. object MyContextModule {
  4.     @Provides
  5.     @GlobalContext
  6.     fun provideContext(): Context? {
  7.         return MyApp.myapp
  8.     }
  9. }
  10. @Qualifier
  11. @Retention(AnnotationRetention.BINARY)
  12. annotation class GlobalContext
复制代码
再注入Context:
  1. class Software @Inject constructor(@GlobalContext val context: Context?) {
  2.     val name = "fish"
  3.     fun getWindowService(): WindowManager?{
  4.         return context?.getSystemService(Context.WINDOW_SERVICE) as? WindowManager
  5.     }
  6. }
复制代码
可以看出,借助@Provides和@Qualifier,可以实现全局的Context。
固然了,现实上我们无需如此麻烦,由于这部门工作Hilt已经预先帮我们弄了。
与我们提供的限定符注解GlobalContext雷同,Hilt预先提供了:
  1. @Qualifier
  2. @Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
  3. public @interface ApplicationContext {}
复制代码
因此我们只必要在必要的地方引用它即可:
  1. class Software @Inject constructor(@ApplicationContext val context: Context?) {
  2.     val name = "fish"
  3.     fun getWindowService(): WindowManager?{
  4.         return context?.getSystemService(Context.WINDOW_SERVICE) as? WindowManager
  5.     }
  6. }
复制代码
如此一来我们无需重新定义Module。
     组件作用域和生命周期

Hilt支持的注入点(类)

以上的demo都是在MyApp里进行依靠,MyApp里使用了注解:@HiltAndroidApp 修饰,表现当前App支持Hilt依靠,Application就是它支持的一个注入点,现在想要在Activity里使用Hilt呢?
  1. @AndroidEntryPoint
  2. class SecondActivity : AppCompatActivity() {
复制代码
除了Application和Activity,Hilt内置支持的注入点如下:

除了Application和ViewModel,其它注入点都是通过使用@AndroidEntryPoint修饰。
   注入点实在就是依靠注入开始的点,比如Activity里必要注入A依靠,A里又必要注入B依靠,B里又必要注入C依靠,从Activity开始我们就能构建所有的依靠
  Hilt组件的生命周期

什么是组件?在Dagger时代我们必要本身写组件,而在Hilt里组件都是主动生成的,无需我们干预。
依靠注入的本质现实上就是在某个地方悄咪咪地创建对象,这个地方的就是组件,Hilt专为Android打造,因此势必适配了Android的特性,比如生命周期这个Android里的重中之重。
因此Hilt的组件有两个重要功能:
     Hilt组件如下:

可以看出,这些组件的创建和销毁深度绑定了Android常见的生命周期。
你大概会说:上面貌似没用到组件干系的东西,看了这么久也没看懂啊。
继承看个例子:
  1. @Module
  2. @InstallIn(SingletonComponent::class)
  3. object HardwareModule {
  4.     @Provides
  5.     fun provideHardware():IHardware {
  6.         return HardwareImpl()
  7.     }
  8. }
复制代码
@InstallIn(SingletonComponent::class) 表现把模块安装到SingletonComponent组件里,SingletonComponent组件顾名思义是全局的,对应的是Application级别。因此安装的这个模块可在整个App里使用。
问题来了:SingletonComponent是不是表现@Provides修饰的函数返回的实例是同一个?
答案是否定的。
这就涉及到组件的作用域。
组件的作用域

想要上一小结的代码提供全局唯一实例,则可用组件作用域注解修饰函数:
  1. @Module
  2. @InstallIn(SingletonComponent::class)
  3. object HardwareModule {
  4.     @Provides
  5.     @Singleton
  6.     fun provideHardware():IHardware {
  7.         return HardwareImpl()
  8.     }
  9. }
复制代码
当我们在任何地方注入IHardware时,获取到的都是同一个实例。
除了@Singleton表现组件的作用域,另有其它对应组件的作用域:

简朴解释作用域:
@Singleton 被它修饰的构造函数或是函数,返回的始终是同一个实例
@ActivityRetainedScoped 被它修饰的构造函数或是函数,在Activity的重修前后返回同一实例
@ActivityScoped 被它修饰的构造函数或是函数,在同一个Activity对象里,返回的都是同一实例
@ViewModelScoped 被它修饰的构造函数或是函数,与ViewModel规则一致
     以下几种写法都不符合第二种限制:
  1. @Module
  2. @InstallIn(SingletonComponent::class)
  3. object HardwareModule {
  4.     @Provides
  5.     @ActivityScoped//错误,和组件的作用域不一致
  6.     fun provideHardware():IHardware {
  7.         return HardwareImpl()
  8.     }
  9. }
  10. @Module
  11. @InstallIn(ActivityComponent::class)
  12. object HardwareModule {
  13.     @Provides
  14.     @Singleton//错误,和组件的作用域不一致
  15.     fun provideHardware():IHardware {
  16.         return HardwareImpl()
  17.     }
  18. }
  19. @Module
  20. @InstallIn(ActivityRetainedComponent::class)
  21. object HardwareModule {
  22.     @Provides
  23.     @ActivityScoped//错误,和组件的作用域不一致
  24.     fun provideHardware():IHardware {
  25.         return HardwareImpl()
  26.     }
  27. }
复制代码
除了修饰Module,作用域还可以用于修饰构造函数:
  1. @ActivityScoped
  2. class Hardware @Inject constructor(){
  3.     fun printName() {
  4.         println("I'm fish")
  5.     }
  6. }
复制代码
@ActivityScoped表现不管注入几个Hardware,在同一个Activity里注入的实例都是一致的。
构造函数里无法注入的字段

一个类的构造函数假如被@Inject注入,那么构造函数的其它参数都必要支持注入。
  1. class Hardware @Inject constructor(val context: Context) {
  2.     fun printName() {
  3.         println("I'm fish")
  4.     }
  5. }
复制代码
以上代码是无法编译通过的,由于Context不支持注入,而通过上面的分析可知,我们可以使用限定符:
  1. class Hardware @Inject constructor(@ApplicationContext val context: Context) {
  2.     fun printName() {
  3.         println("I'm fish")
  4.     }
  5. }
复制代码
这就可以成功注入了。
再看看此种场景:
  1. class Hardware @Inject constructor(
  2.     @ApplicationContext val context: Context,
  3.     val version: String,
  4. ) {
  5.     fun printName() {
  6.         println("I'm fish")
  7.     }
  8. }
复制代码
很显然String不支持注入,固然我们可以向@ApplicationContext 一样也给String提供一个@Provides和@Qualifier注解,但可想而知很麻烦,关键是String是动态变革的,我们确实必要Hardware构造的时候传入合适的String。
由此引入新的写法:辅助注入
  1. class Hardware @AssistedInject constructor(
  2.     @ApplicationContext val context: Context,
  3.     @Assisted
  4.     val version: String,
  5. ) {
  6.     //辅助工厂类
  7.     @AssistedFactory
  8.     interface Factory{
  9.         //不支持注入的参数都可以放这,返回值为待注入的类型
  10.         fun create(version: String):Hardware
  11.     }
  12.     fun printName() {
  13.         println("I'm fish")
  14.     }
  15. }
复制代码
在引用注入的地方不能直接使用Hardware,而是必要通过辅助工厂进行创建:
  1. @AndroidEntryPoint
  2. class SecondActivity : AppCompatActivity() {
  3.     private lateinit var binding: ActivitySecondBinding    @Inject    lateinit var hardwareFactory : Hardware.Factory        override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        binding = ActivitySecondBinding.inflate(layoutInflater)        setContentView(binding.root)        val hardware = hardwareFactory.create("3.3.2")        println("${hardware.printName()}")    }}
复制代码
如此一来,通过辅助注入,我们照旧可以使用Hilt,值得一提的是辅助注入不是Hilt独有,而是从Dagger继承来的功能。
自定义注入点

Hilt仅仅内置了常用的注入点:Application、Activity、Fragment、ViewModel等。
思考一种场景:小明同砚写的模块都是必要注入:
  1. class Hardware @Inject constructor(
  2.     val gpu: GPU,
  3.     val cpu: CPU,
  4. ) {
  5.     fun printName() {
  6.         println("I'm fish")
  7.     }
  8. }
  9. class GPU @Inject constructor(val videoStorage: VideoStorage){}
  10. //显存
  11. class VideoStorage @Inject constructor() {}
  12. class CPU @Inject constructor(val register: Register) {}
  13. //寄存器
  14. class Register @Inject() constructor() {}
复制代码
此时小刚必要引用Hardware,他有两种选择:
     这个时候适合小刚的方案是:
   自定义注入点
  方案实行步骤:
一:定义入口点
  1. @InstallIn(SingletonComponent::class)
  2. interface HardwarePoint {
  3.     //该注入点负责返回Hardware实例
  4.     fun getHardware(): Hardware
  5. }
复制代码
二:通过入口点获取实例
  1. class XiaoGangPhone {
  2.     fun getHardware(context: Context):Hardware {
  3.         val entryPoint = EntryPointAccessors.fromApplication(context, HardwarePoint::class.java)
  4.         return entryPoint.getHardware()
  5.     }
  6. }
复制代码
三:使用Hardware
  1.         val hardware = XiaoGangPhone().getHardware(this)
  2.         println("${hardware.printName()}")
复制代码
注入object类

定义了object类,但在注入的时候也必要,可以做如下处理:
  1. object MySystem {
  2.     fun getSelf():MySystem {
  3.         return this
  4.     }
  5.     fun printName() {
  6.         println("I'm fish")
  7.     }
  8. }
  9. @Module
  10. @InstallIn(SingletonComponent::class)
  11. object MiddleModule {
  12.     @Provides
  13.     @Singleton
  14.     fun provideSystem():MySystem {
  15.         return MySystem.getSelf()
  16.     }
  17. }
  18. //使用注入
  19. class Middleware @Inject constructor(
  20.     val mySystem:MySystem
  21. ) {
  22. }
复制代码
4. Hilt 原理简朴分析

  1. @AndroidEntryPoint
  2. class SecondActivity : AppCompatActivity() {
  3. }
复制代码
Hilt通过apt在编译时期生成代码:
  1. public abstract class Hilt_SecondActivity extends AppCompatActivity implements GeneratedComponentManagerHolder {
  2.    
  3.     private boolean injected = false;
  4.     Hilt_SecondActivity() {
  5.         super();
  6.         //初始化注入监听
  7.         _initHiltInternal();
  8.     }
  9.     Hilt_SecondActivity(int contentLayoutId) {
  10.         super(contentLayoutId);
  11.         _initHiltInternal();
  12.     }
  13.     private void _initHiltInternal() {
  14.         addOnContextAvailableListener(new OnContextAvailableListener() {
  15.             @Override
  16.             public void onContextAvailable(Context context) {
  17.                 //真正注入
  18.                 inject();
  19.             }
  20.         });
  21.     }
  22.     protected void inject() {
  23.         if (!injected) {
  24.             injected = true;
  25.             //通过manager获取组件,再通过组件注入
  26.             ((SecondActivity_GeneratedInjector) this.generatedComponent()).injectSecondActivity(UnsafeCasts.<SecondActivity>unsafeCast(this));
  27.         }
  28.     }
  29. }
复制代码
在编译期,SecondActivity的父类由AppCompatActivity变为Hilt_SecondActivity,因此当SecondActivity构造时就会调用父类的构造器监听create()的回调,回调调用时进行注入。
   由此可见,Activity.onCreate()执行后,Hilt依靠注入的字段才会有值
  真正注入的过程涉及到不少的类,都是主动生成的类,有兴趣可以对着源码查找流程,此处就不睁开说了。
5. Android到底该不该使用DI框架?

有人说DI比较复杂,还不如我直接构造呢?
又有人说那是你项目不复杂,用不到,在后端流行的Spring全家桶,依靠注入大行其道,Android复杂的项目也必要DI来解耦。
从个人的实践经验看,Android MVVM/MVI 模式照旧比较适合引入Hilt的。

摘抄官网的:当代Android 应用架构
通常来说我们这么计划UI层到数据层的架构:
  1. class MyViewModel @Inject constructor(
  2.     val repository: LoginRepository
  3. ) :ViewModel() {}
  4. class LoginRepository @Inject constructor(
  5.     val rds : RemoteDataSource,
  6.     val lds : LocalDataSource
  7. ) {}
  8. //远程来源
  9. class RemoteDataSource @Inject constructor(
  10.     val myRetrofit: MyRetrofit
  11. ) {}
  12. class MyRetrofit @Inject constructor(
  13. ) {}
  14. //本地来源
  15. class LocalDataSource @Inject constructor(
  16.     val myDataStore: MyDataStore
  17. ) {}
  18. class MyDataStore @Inject constructor() {}
复制代码
可以看出,层次比较深,使用了Hilt简洁了许多。
本文基于 Hilt 2.48.1
参考文档:
https://dagger.dev/hilt/gradle-setup
https://developer.android.com/topic/architecture/recommendations?hl=zh-cn
https://repo.maven.apache.org/maven2/com/google/dagger/hilt/android/com.google.dagger.hilt.android.gradle.plugin/

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4