前语

之前接手的一个项目里有些代码看得云里雾里的,找了半天没有找到方针创立的当地,后来才发现本来运用了Hilt进行了依靠注入。Hilt比较Dagger虽然现已比较简练,但对初学者来说仍是有些门槛,并且网上的许多文章都是搬自官网,下手简略深化难,假如你对Hilt不了解或是想了解得更多,那么接下来的内容将助力你玩转Hilt。

经过本篇文章,你将了解到:

  1. 什么是依靠注入?
  2. Hilt 的引进与根本运用
  3. Hilt 的进阶运用
  4. Hilt 原理简略剖析
  5. Android到底该不该运用DI结构?

1. 什么是依靠注入?

什么是依靠?

手机为例,要拼装一台手机,咱们需求哪些部件呢?
从微观上分类:软件+硬件。
由此咱们能够说:手机依靠了软件和硬件。
而反映到代码的国际:

class FishPhone(){
    val software = Software()
    val hardware = Hardware()
    fun call() {
        //打电话
        software.handle()
        hardware.handle()
    }
}
//软件
class Software() {
    fun handle(){}
}
//硬件
class Hardware() {
    fun handle(){}
}

FishPhone 依靠了两个方针:别离是Software和Hardware。
Software和Hardware是FishPhone的依靠(项)。

什么是注入?

上面的Demo,FishPhone内部自主结构了依靠项的实例,考虑到依靠的改变挺大的,每次依靠项的改变都要改动到FishPhone,简略出错,也不是那么灵活,因而考虑从外部将依靠传进来,这种方法称之为:依靠注入(Dependency Injection 简称DI)
有几种方法:

  1. 结构函数传入
  2. SetXX函数传入
  3. 从其它方针间接获取

结构函数依靠注入:

class FishPhone(val software: Software, val hardware: Hardware){
    fun call() {
        //打电话
        software.handle()
        hardware.handle()
    }
}

FishPhone的功用比较纯粹便是打电话功用,而依靠项都是外部传入提升了灵活性。

为什么需求依靠注入结构?

手机制造出来后交给客户运用。

class Customer() {
    fun usePhone() {
        val software = Software()
        val hardware = Hardware()
        FishPhone(software, hardware).call()
    }
}

用户想运用手机打电话,还得自己创立软件和硬件,这个手机还能卖出去吗?
而不想创立软件和硬件那得让FishPhone自己担任去创立,那不是又回到上面的场景了吗?

你可能会说:FishPhone内部就依靠了两个方针罢了,自己担任创立又怎么了?

解耦

再看看如下Demo:

interface ISoftware {
    fun handle()
}
//硬件
interface IHardware {
    fun handle()
}
//软件
class SoftwareImpl() : ISoftware {
    override fun handle() {}
}
//硬件
class HardwareImpl : IHardware {
    override fun handle() {}
}
class FishPhone() {
    val software: ISoftware = SoftwareImpl()
    val hardware: IHardware = HardwareImpl()
    fun call() {
        //打电话
        software.handle()
        hardware.handle()
    }
}

FishPhone 只关注软件和硬件的接口,至于详细怎么完成它不关怀,这就到达了解耦的目的。 既然要解耦,那么SoftwareImpl()、HardwareImpl()就不能出现在FishPhone里。
应该改为如下方法:

class FishPhone(val software: ISoftware, val hardware: IHardware) {
    fun call() {
        //打电话
        software.handle()
        hardware.handle()
    }
}

消除模板代码

即使咱们不考虑解耦,假若HardwareImpl里又依靠了cpu、gpu、disk等模块:

//硬件
class HardwareImpl : IHardware {
    val cpu = CPU(Regisgter(), Cal(), Bus())
    val gpu = GPU(Image(), Video())
    val disk = Disk(Block(), Flash())
    //...其它模块
    override fun handle() {}
}

现在只是只是三个模块,若是依靠更多的模块或许模块的自身也需求依靠其它子模块,比方CPU需求依靠寄存器、运算单元等等,那么咱们就需求写更多的模板代码,要是咱们只需求声明一下想要运用的方针而不必管它的创立就好了。

class HardwareImpl(val cpu: CPU, val gpu: GPU, val disk: Disk) : IHardware {
    override fun handle() {}
}

能够看出,下面的代码比上面的简练多了。

  1. 从解耦和消除模板代码的视点看,咱们迫切需求一个能够主动创立依靠方针并且将依靠注入到方针代码的结构,这便是依靠注入结构
  2. 依靠注入结构能够办理依靠方针的创立,依靠方针的注入,依靠方针的生命周期
  3. 运用者只是只需求标明自己需求什么类型的方针,剩余的无需关怀,都由结构主动完结

先想想若是咱们想要完成这样的结构需求怎么做呢?
相信许多小伙伴最朴素的主意便是:运用工厂模式,你传参告知我想要什么方针我给你结构出来。
这个主意是半主动注入,由于咱们还要调用工厂方法去获取,而全主动的注入一般来说是运用注解标示完成的。

2. Hilt 的引进与根本运用

Hilt的引进

从Dagger到Dagger2再到Hilt(Android专用),装备越来越简略也比较简略上手。
前面说了依靠注入结构的必要性,咱们就想刻不容缓的上手,但难度可想而知,还好大神们早就造好了轮子。
以AGP 7.0 以上为例,来看看Hilt结构是怎么引进的。

一:project等级的build.gradle 引进如下代码:

plugins {
    //指定插件地址和版别
    id 'com.google.dagger.hilt.android' version '2.48.1' apply false
}

二:module等级的build.gradle引进如下代码:

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    //运用插件
    id 'com.google.dagger.hilt.android'
    //kapt生成代码
    id 'kotlin-kapt'
}
//引进库
implementation 'com.google.dagger:hilt-android:2.48.1'
kapt 'com.google.dagger:hilt-compiler:2.48.1'

实时更新最新版别以及AGP7.0以下的引证请参阅:Hilt最新版别装备

Hilt的简略运用

前置过程整好了接下来看看怎么运用。

一:标明该App能够运用Hilt来进行依靠注入,添加如下代码:

@HiltAndroidApp
class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
    }
}

@HiltAndroidApp 添加到App的进口,即表明依靠注入的环境现已搭建好。

二:注入一个方针到MyApp里:
有个类界说如下:

class Software {
    val name = "fish"
}

咱们不想显示的结构它,想凭借Hilt注入它,那得先告知Hilt这个类你帮我注入一下,改为如下代码:

class Software @Inject constructor() {
    val name = "fish"
}

在结构函数前添加了@Inject注解,表明该类能够被注入。
而在MyApp里运用Software方针:

@HiltAndroidApp
class MyApp : Application() {
    @Inject
    lateinit var software: Software
    override fun onCreate() {
        super.onCreate()
        println("inject result:${software.name}")
    }
}

对引证的方针运用@Inject注解,表明期望Hilt帮我将这个方针new出来。
最终检查打印输出正确,阐明Software方针被创立了。

这是最简略的Hilt运用,能够看出:

  1. 咱们并没有显式地创立Software方针,而Hilt在恰当的时分就帮咱们创立好了
  2. @HiltAndroidApp 只用于润饰Application

怎么注入接口?

一:过错演示 上面提到过,运用DI的好处之一便是解耦,而咱们上面注入的是类,现在咱们将Software笼统为接口,很简略就会想到如下写法:

interface ISoftware {
    fun printName()
}
class SoftwareImpl @Inject constructor(): ISoftware{
    override fun printName() {
        println("name is fish")
    }
}
@HiltAndroidApp
class MyApp : Application() {
    @Inject
    lateinit var software: ISoftware
    override fun onCreate() {
        super.onCreate()
        println("inject result:${software.printName()}")
    }
}

不幸的是上述代码编译失败,Hilt提示说不能对接口运用注解,由于咱们并没有告知Hilt是谁完成了ISoftware,而接口自身不能直接实例化,因而咱们需求为它指定详细的完成类。

二:正确演示
再界说一个类如下:

@Module
@InstallIn(SingletonComponent::class)
abstract class SoftwareModule {
    @Binds
    abstract fun bindSoftware(impl: SoftwareImpl):ISoftware
}
  1. @Module 表明该类是一个Hilt的Module,固定写法
  2. @InstallIn 表明模块在哪个组件生命周期内收效,SingletonComponent::class指的是大局
  3. 一个笼统类,类名随意
  4. 笼统方法,方法名随意,回来值是需求被注入的方针类型(接口),而参数是该接口的完成类,运用@Binds注解符号,

如此一来咱们就告知了Hilt,SoftwareImpl是ISoftware的完成类,所以Hilt注入ISoftware方针的时分就知道运用SoftwareImpl进行实例化。 其它不变运行一下:

Android运用Hilt依靠注入,让人看不懂你代码

能够看出,实践注入的是SoftwareImpl。

@Binds 适用在咱们能够修正类的结构函数的场景

怎么注入第三方类

上面的SoftwareImpl是咱们能够修正的,由于运用了@Inject润饰其结构函数,所以能够在其它当地注入它。
在一些时分咱们不想运用@Inject润饰或许说这个类咱们不能修正,那该怎么注入它们呢?

一:界说Provides模块

@Module
@InstallIn(SingletonComponent::class)
object HardwareModule {
    @Provides
    fun provideHardware():Hardware {
        return Hardware()
    }
}
  1. @Module和@InstallIn 注解是必须的
  2. 界说object类
  3. 界说函数,方法名随意,回来类型为咱们需求注入的类型
  4. 函数体里经过结构或是其它方法创立详细实例
  5. 运用@Provides注解函数

二:依靠运用
而Hardware界说如下:

class Hardware {
    fun printName() {
        println("I'm fish")
    }
}

在MyApp里引证Hardware:

Android运用Hilt依靠注入,让人看不懂你代码

虽然Hardware结构函数没有运用@Inject注解,但是咱们仍然能够运用依靠注入。

当然咱们也能够注入接口:

interface IHardware {
    fun printName()
}
class HardwareImpl : IHardware {
    override fun printName() {
        println("name is fish")
    }
}

想要注入IHardware接口,需求界说provides模块:

@Module
@InstallIn(SingletonComponent::class)
object HardwareModule {
    @Provides
    fun provideHardware():IHardware {
        return HardwareImpl()
    }
}

@Provides适用于无法修正类的结构函数的场景,多用于注入第三方的方针

3. Hilt 的进阶运用

限定符

上述 ISoftware的完成类只有一个,假设现在有两个完成类呢?
比方说这些软件能够是美国供给,也能够是中国供给的,根据上面的经历咱们很简略写出如下代码:

class SoftwareChina @Inject constructor() : ISoftware {
    override fun printName() {
        println("from china")
    }
}
class SoftwareUS @Inject constructor() : ISoftware {
    override fun printName() {
        println("from US")
    }
}
@Module
@InstallIn(SingletonComponent::class)
abstract class SoftwareModule {
    @Binds
    abstract fun bindSoftwareCh(impl: SoftwareChina):ISoftware
    @Binds
    abstract fun bindSoftwareUs(impl: SoftwareUS):ISoftware
}
//依靠注入:
@Inject
lateinit var software: ISoftware

兴致勃勃的进行编译,但是却报错:

Android运用Hilt依靠注入,让人看不懂你代码

也便是说Hilt想要注入ISoftware,但不知道挑选哪个完成类,SoftwareChina仍是SoftwareUS?没人告知它,所以它迷茫了,干脆都绑定了。

这个时分咱们需求凭借注解:@Qualifier 限定符注解来对完成类进行约束。
改造一下:

@Module
@InstallIn(SingletonComponent::class)
abstract class SoftwareModule {
    @Binds
    @China
    abstract fun bindSoftwareCh(impl: SoftwareChina):ISoftware
    @Binds
    @US
    abstract fun bindSoftwareUs(impl: SoftwareUS):ISoftware
}
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class US
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class China

界说新的注解类,运用@Qualifier润饰。
而后在Module里,别离运用注解类润饰回来的函数,如bindSoftwareCh函数指定回来SoftwareChina来完成ISoftware接口。

最终在引证依靠注入的当地别离运用@China @US润饰。

    @Inject
    @US
    lateinit var software1: ISoftware
    @Inject
    @China
    lateinit var software2: ISoftware

此刻,虽然software1、software2都是ISoftware类型,但是由于咱们指定了限定符@US、@China,因而最终真正的完成类别离是SoftwareChina、SoftwareUS。

@Qualifier 首要用在接口有多个完成类(笼统类有多个子类)的注入场景

预界说限定符

上面提及的限定符咱们还能够扩展其运用方法。
你可能发现了,上述提及的可注入的类结构函数都是无参的,许多时分咱们的结构函数是需求有参数的,比方:

class Software @Inject constructor(val context: Context) {
    val name = "fish"
    fun getWindowService(): WindowManager?{
        return context.getSystemService(Context.WINDOW_SERVICE) as? WindowManager
    }
}
//注入
@Inject
lateinit var software: Software

这个时分编译会报错:

Android运用Hilt依靠注入,让人看不懂你代码
意思是Software依靠的Context没有进行注入,因而咱们需求给它注入一个Context。

由上面的剖析可知,Context类不是咱们能够修正的,只能经过@Provides方法供给其注入实例,并且Context有许多子类,咱们需求运用@Qualifier指定详细完成类,因而很简略咱们就想到如下对策。
先界说Module:

@Module
@InstallIn(SingletonComponent::class)
object MyContextModule {
    @Provides
    @GlobalContext
    fun provideContext(): Context? {
        return MyApp.myapp
    }
}
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class GlobalContext

再注入Context:

class Software @Inject constructor(@GlobalContext val context: Context?) {
    val name = "fish"
    fun getWindowService(): WindowManager?{
        return context?.getSystemService(Context.WINDOW_SERVICE) as? WindowManager
    }
}

能够看出,凭借@Provides和@Qualifier,能够完成大局的Context。
当然了,实践上咱们无需如此费事,由于这部分工作Hilt现已预先帮咱们弄了。
与咱们供给的限定符注解GlobalContext相似,Hilt预先供给了:

@Qualifier
@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
public @interface ApplicationContext {}

因而咱们只需求在需求的当地引证它即可:

class Software @Inject constructor(@ApplicationContext val context: Context?) {
    val name = "fish"
    fun getWindowService(): WindowManager?{
        return context?.getSystemService(Context.WINDOW_SERVICE) as? WindowManager
    }
}

如此一来咱们无需从头界说Module。

  1. 除了供给Application等级的上下文:@ApplicationContext,Hilt还供给了Activity等级的上下文:@ActivityContext,由所以Hilt内置的限定符,因而称为预界说限定符。
  2. 假如想自己供给限定符,能够参照GlobalContext的做法。

组件效果域和生命周期

Hilt支撑的注入点(类)

以上的demo都是在MyApp里进行依靠,MyApp里运用了注解:@HiltAndroidApp 润饰,表明当前App支撑Hilt依靠,Application便是它支撑的一个注入点,现在想要在Activity里运用Hilt呢?

@AndroidEntryPoint
class SecondActivity : AppCompatActivity() {

除了Application和Activity,Hilt内置支撑的注入点如下:

Android运用Hilt依靠注入,让人看不懂你代码

除了Application和ViewModel,其它注入点都是经过运用@AndroidEntryPoint润饰。

注入点其实便是依靠注入开端的点,比方Activity里需求注入A依靠,A里又需求注入B依靠,B里又需求注入C依靠,从Activity开端咱们就能构建一切的依靠

Hilt组件的生命周期

什么是组件?在Dagger时代咱们需求自己写组件,而在Hilt里组件都是主动生成的,无需咱们干涉。 依靠注入的本质实践上便是在某个当地悄咪咪地创立方针,这个当地的便是组件,Hilt专为Android打造,因而势必适配了Android的特性,比方生命周期这个Android里的重中之重。
因而Hilt的组件有两个首要功用:

  1. 创立、注入依靠的方针
  2. 办理方针的生命周期

Hilt组件如下:

Android运用Hilt依靠注入,让人看不懂你代码

能够看出,这些组件的创立和销毁深度绑定了Android常见的生命周期。
你可能会说:上面形似没用到组件相关的东西,看了这么久也没看懂啊。
继续看个比如:

@Module
@InstallIn(SingletonComponent::class)
object HardwareModule {
    @Provides
    fun provideHardware():IHardware {
        return HardwareImpl()
    }
}

@InstallIn(SingletonComponent::class) 表明把模块安装到SingletonComponent组件里, SingletonComponent组件顾名思义是大局的,对应的是Application等级。因而安装的这个模块可在整个App里运用。

问题来了:SingletonComponent是不是表明@Provides润饰的函数回来的实例是同一个?
答案是否定的。

这就涉及到组件的效果域。

组件的效果域

想要上一小结的代码供给大局仅有实例,则可用组件效果域注解润饰函数:

@Module
@InstallIn(SingletonComponent::class)
object HardwareModule {
    @Provides
    @Singleton
    fun provideHardware():IHardware {
        return HardwareImpl()
    }
}

当咱们在任何当地注入IHardware时,获取到的都是同一个实例。
除了@Singleton表明组件的效果域,还有其它对应组件的效果域:

Android运用Hilt依靠注入,让人看不懂你代码

简略解释效果域:
@Singleton 被它润饰的结构函数或是函数,回来的始终是同一个实例
@ActivityRetainedScoped 被它润饰的结构函数或是函数,在Activity的重建前后回来同一实例
@ActivityScoped 被它润饰的结构函数或是函数,在同一个Activity方针里,回来的都是同一实例
@ViewModelScoped 被它润饰的结构函数或是函数,与ViewModel规矩共同

  1. Hilt默认不绑定任何效果域,由此带来的结果是每一次注入都是全新的方针
  2. 组件的效果域要么不指定,要指定那必须和组件的生命周期共同

以下几种写法都不契合第二种约束:

@Module
@InstallIn(SingletonComponent::class)
object HardwareModule {
    @Provides
    @ActivityScoped//过错,和组件的效果域不共同
    fun provideHardware():IHardware {
        return HardwareImpl()
    }
}
@Module
@InstallIn(ActivityComponent::class)
object HardwareModule {
    @Provides
    @Singleton//过错,和组件的效果域不共同
    fun provideHardware():IHardware {
        return HardwareImpl()
    }
}
@Module
@InstallIn(ActivityRetainedComponent::class)
object HardwareModule {
    @Provides
    @ActivityScoped//过错,和组件的效果域不共同
    fun provideHardware():IHardware {
        return HardwareImpl()
    }
}

除了润饰Module,效果域还能够用于润饰结构函数:

@ActivityScoped
class Hardware @Inject constructor(){
    fun printName() {
        println("I'm fish")
    }
}

@ActivityScoped表明不论注入几个Hardware,在同一个Activity里注入的实例都是共同的。

结构函数里无法注入的字段

一个类的结构函数假如被@Inject注入,那么结构函数的其它参数都需求支撑注入。

class Hardware @Inject constructor(val context: Context) {
    fun printName() {
        println("I'm fish")
    }
}

以上代码是无法编译经过的,由于Context不支撑注入,而经过上面的剖析可知,咱们能够运用限定符:

class Hardware @Inject constructor(@ApplicationContext val context: Context) {
    fun printName() {
        println("I'm fish")
    }
}

这就能够成功注入了。

再看看此种场景:

class Hardware @Inject constructor(
    @ApplicationContext val context: Context,
    val version: String,
) {
    fun printName() {
        println("I'm fish")
    }
}

很显然String不支撑注入,当然咱们能够向@ApplicationContext 一样也给String供给一个@Provides和@Qualifier注解,但可想而知很费事,要害是String是动态改变的,咱们的确需求Hardware结构的时分传入适宜的String。

由此引进新的写法:辅佐注入

class Hardware @AssistedInject constructor(
    @ApplicationContext val context: Context,
    @Assisted
    val version: String,
) {
    //辅佐工厂类
    @AssistedFactory
    interface Factory{
        //不支撑注入的参数都能够放这,回来值为待注入的类型
        fun create(version: String):Hardware
    }
    fun printName() {
        println("I'm fish")
    }
}

在引证注入的当地不能直接运用Hardware,而是需求经过辅佐工厂进行创立:

@AndroidEntryPoint
class SecondActivity : AppCompatActivity() {
    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等。
思考一种场景:小明同学写的模块都是需求注入:

class Hardware @Inject constructor(
    val gpu: GPU,
    val cpu: CPU,
) {
    fun printName() {
        println("I'm fish")
    }
}
class GPU @Inject constructor(val videoStorage: VideoStorage){}
//显存
class VideoStorage @Inject constructor() {}
class CPU @Inject constructor(val register: Register) {}
//寄存器
class Register @Inject() constructor() {}

此刻小刚需求引证Hardware,他有两种挑选:

  1. 运用注入方法很简略就引证了Hardware,可惜的是他没有注入点,只是只是东西类。
  2. 不选注入方法,则需求结构Hardware实例,而Hardware依靠GPU和CPU,它们又别离依靠VideoStorage和Register,想要成功结构Hardware实例需求将其它的依靠实例都手动结构出来,可想而知很费事。

这个时分合适小刚的计划是:

自界说注入点

计划施行过程:
一:界说进口点

@InstallIn(SingletonComponent::class)
interface HardwarePoint {
    //该注入点担任回来Hardware实例
    fun getHardware(): Hardware
}

二:经过进口点获取实例

class XiaoGangPhone {
    fun getHardware(context: Context):Hardware {
        val entryPoint = EntryPointAccessors.fromApplication(context, HardwarePoint::class.java)
        return entryPoint.getHardware()
    }
}

三:运用Hardware

        val hardware = XiaoGangPhone().getHardware(this)
        println("${hardware.printName()}")

注入object类

界说了object类,但在注入的时分也需求,能够做如下处理:

object MySystem {
    fun getSelf():MySystem {
        return this
    }
    fun printName() {
        println("I'm fish")
    }
}
@Module
@InstallIn(SingletonComponent::class)
object MiddleModule {
    @Provides
    @Singleton
    fun provideSystem():MySystem {
        return MySystem.getSelf()
    }
}
//运用注入
class Middleware @Inject constructor(
    val mySystem:MySystem
) {
}

4. Hilt 原理简略剖析

@AndroidEntryPoint
class SecondActivity : AppCompatActivity() {}

Hilt经过apt在编译时期生成代码:

public abstract class Hilt_SecondActivity extends AppCompatActivity implements GeneratedComponentManagerHolder {
    private boolean injected = false;
    Hilt_SecondActivity() {
        super();
        //初始化注入监听
        _initHiltInternal();
    }
    Hilt_SecondActivity(int contentLayoutId) {
        super(contentLayoutId);
        _initHiltInternal();
    }
    private void _initHiltInternal() {
        addOnContextAvailableListener(new OnContextAvailableListener() {
            @Override
            public void onContextAvailable(Context context) {
                //真正注入
                inject();
            }
        });
    }
    protected void inject() {
        if (!injected) {
            injected = true;
            //经过manager获取组件,再经过组件注入
            ((SecondActivity_GeneratedInjector) this.generatedComponent()).injectSecondActivity(UnsafeCasts.<SecondActivity>unsafeCast(this));
        }
    }
}

在编译期,SecondActivity的父类由AppCompatActivity变为Hilt_SecondActivity,因而当SecondActivity结构时就会调用父类的结构器监听create()的回调,回调调用时进行注入。

由此可见,Activity.onCreate()履行后,Hilt依靠注入的字段才会有值

真正注入的过程涉及到不少的类,都是主动生成的类,有爱好能够对着源码查找流程,此处就不展开说了。

5. Android到底该不该运用DI结构?

有人说DI比较杂乱,还不如我直接结构呢?
又有人说那是你项目不杂乱,用不到,在后端盛行的Spring全家桶,依靠注入大行其道,Android杂乱的项目也需求DI来解耦。

从个人的实践经历看,Android MVVM/MVI 模式仍是比较合适引进Hilt的。

Android运用Hilt依靠注入,让人看不懂你代码

摘抄官网的:现代Android 运用架构
一般来说咱们这么规划UI层到数据层的架构:

class MyViewModel @Inject constructor(
    val repository: LoginRepository
) :ViewModel() {}
class LoginRepository @Inject constructor(
    val rds : RemoteDataSource,
    val lds : LocalDataSource
) {}
//远程来历
class RemoteDataSource @Inject constructor(
    val myRetrofit: MyRetrofit
) {}
class MyRetrofit @Inject constructor(
) {}
//本地来历
class LocalDataSource @Inject constructor(
    val myDataStore: MyDataStore
) {}
class MyDataStore @Inject constructor() {}

能够看出,层次比较深,运用了Hilt简练了许多。

本文根据 Hilt 2.48.1
参阅文档:
dagger.dev/hilt/gradle…
developer.android.com/topic/archi…
repo.maven.apache.org/maven2/com/…