ToB企服应用市场:ToB评测及商务社交产业平台
标题:
Android使用Hilt依靠注入,让人看不懂你代码
[打印本页]
作者:
熊熊出没
时间:
2024-7-29 20:51
标题:
Android使用Hilt依靠注入,让人看不懂你代码
前言
之前接手的一个项目里有些代码看得云里雾里的,找了半天没有找到对象创建的地方,厥后才发现原来使用了Hilt进行了依靠注入。Hilt相比Dagger虽然已经比较简洁,但对初学者来说照旧有些门槛,并且网上的许多文章都是搬自官网,入手容易深入难,假如你对Hilt不相识或是想相识得更多,那么接下来的内容将助力你玩转Hilt。
通过本篇文章,你将相识到:
什么是依靠注入?
Hilt 的引入与根本使用
Hilt 的进阶使用
Hilt 原理简朴分析
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)
有几种方式:
构造函数传入
SetXX函数传入
从其它对象间接获取
构造函数依靠注入:
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() {}
}
复制代码
可以看出,下面的代码比上面的简洁多了。
从解耦和消除模板代码的角度看,我们迫切必要一个可以或许主动创建依靠对象并且将依靠注入到目标代码的框架,这就是依靠注入框架
依靠注入框架可以或许管理依靠对象的创建,依靠对象的注入,依靠对象的生命周期
使用者仅仅只必要表明本身必要什么范例的对象,剩下的无需关心,都由框架主动完成
先想想如果我们想要实现如许的框架必要怎么做呢?
信赖许多小伙伴最质朴的想法就是:使用工厂模式,你传参告诉我想要什么对象我给你构造出来。
这个想法是半主动注入,由于我们还要调用工厂方法去获取,而全主动的注入通常来说是使用注解标注实现的。
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应用,可以看出:
我们并没有显式地创建Software对象,而Hilt在得当的时候就帮我们创建好了
@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
}
复制代码
@Module 表现该类是一个Hilt的Module,固定写法
@InstallIn 表现模块在哪个组件生命周期内生效,SingletonComponent::class指的是全局
一个抽象类,类名随意
抽象方法,方法名随意,返回值是必要被注入的对象范例(接口),而参数是该接口的实现类,使用@Binds注解标记,
如此一来我们就告诉了Hilt,SoftwareImpl是ISoftware的实现类,于是Hilt注入ISoftware对象的时候就知道使用SoftwareImpl进行实例化。
其它稳定运行一下:
可以看出,现实注入的是SoftwareImpl。
@Binds 适用在我们可以或许修改类的构造函数的场景
如何注入第三方类
上面的SoftwareImpl是我们可以修改的,由于使用了@Inject修饰其构造函数,所以可以在其它地方注入它。
在一些时候我们不想使用@Inject修饰大概说这个类我们不能修改,那该如何注入它们呢?
一:定义Provides模块
@Module
@InstallIn(SingletonComponent::class)
object HardwareModule {
@Provides
fun provideHardware():Hardware {
return Hardware()
}
}
复制代码
@Module和@InstallIn 注解是必须的
定义object类
定义函数,方法名随意,返回范例为我们必要注入的范例
函数体里通过构造或是其它方式创建具体实例
使用@Provides注解函数
二:依靠使用
而Hardware定义如下:
class Hardware {
fun printName() {
println("I'm fish")
}
}
复制代码
在MyApp里引用Hardware:
虽然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
复制代码
欢欣鼓舞的进行编译,然而却报错:
也就是说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
复制代码
这个时候编译会报错:
意思是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。
除了提供Application级别的上下文:@ApplicationContext,Hilt还提供了Activity级别的上下文:@ActivityContext,由于是Hilt内置的限定符,因此称为预定义限定符。
假如想本身提供限定符,可以参照GlobalContext的做法。
组件作用域和生命周期
Hilt支持的注入点(类)
以上的demo都是在MyApp里进行依靠,MyApp里使用了注解:@HiltAndroidApp 修饰,表现当前App支持Hilt依靠,Application就是它支持的一个注入点,现在想要在Activity里使用Hilt呢?
@AndroidEntryPoint
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常见的生命周期。
你大概会说:上面貌似没用到组件干系的东西,看了这么久也没看懂啊。
继承看个例子:
@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表现组件的作用域,另有其它对应组件的作用域:
简朴解释作用域:
@Singleton 被它修饰的构造函数或是函数,返回的始终是同一个实例
@ActivityRetainedScoped 被它修饰的构造函数或是函数,在Activity的重修前后返回同一实例
@ActivityScoped 被它修饰的构造函数或是函数,在同一个Activity对象里,返回的都是同一实例
@ViewModelScoped 被它修饰的构造函数或是函数,与ViewModel规则一致
Hilt默认不绑定任何作用域,由此带来的结果是每一次注入都是全新的对象
组件的作用域要么不指定,要指定那必须和组件的生命周期一致
以下几种写法都不符合第二种限制:
@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,他有两种选择:
使用注入方式很容易就引用了Hardware,可惜的是他没有注入点,仅仅只是工具类。
不选注入方式,则必要构造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 应用架构
通常来说我们这么计划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
参考文档:
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