Dagger2

1 前言

Dagger是一款为了“降低耦合度”的“依赖注入框架”

1.1 dagger2的前世今生

一开始swankjesse的dagger1先面世。
而后由Google接收并开发出了dagger2版本。
两者都旨在业务逻辑各部分之间的耦合度降低,提高程序比如说实例化对象的可重用性,释放内存中不再需要的对象,从而提高性能。

1.2 依赖注入

依赖注入:
目的是为了“解耦”

  1. 如果要说对于依赖注入最知名的应用,大概就是Java中的Spring框架了。
    Spring在刚开始其实就是一个用于处理依赖注入的框架,后来才慢慢变成了一个功能更加广泛的综合型框架。

  2. Android开发中也有应用。
    比如我们熟悉的ButterKnife。
    而ButterKnife使用的是编译时注解的实现方式,也就是APT技术。

  3. 题外话再说一句,除了APT技术,是否还有其他实现方式 -> 反射。
    在ButterKnife出现之前,我们就是使用的XUtils这个依赖注入框架。
    为什么放弃了使用“反射”实现的XUtils而使用APT实现的ButterKnife呢 -> “反射”性能低。

  4. Dagger也是一样,Dagger1是基于Java反射实现的。
    而Google开发的Dagger2是基于Java注解实现的,这样就把反射的那些弊端全部解决了。

依赖注入.png

2 注解含义及实现

Dagger2 是基于 Java 注解来实现依赖注入的,那么在正式使用之前我们需要先了解下 Dagger2 中的注解。

Dagger2 使用过程中我们通常接触到的注解主要包括:
@Inject, @Module, @Provides, @Component, @Qulifier, @Scope, @Singleton。

2.1 @Inject & @Component

@Inject 有两个作用:

一是用来标记需要依赖的变量,以此告诉 Dagger2 为它提供依赖;
二是用来标记构造函数,Dagger2 通过@Inject 注解可以在需要这个类实例的时候来找到这个构造函数并把相关实例构造出来,以此来为被@Inject 标记了的变量提供依赖;

@Component

@Component 用于标注接口,是依赖需求方和依赖提供方之间的桥梁。
被 Component 标注的接口在编译时会生成该接口的实现类(如果@Component 标注的接口为 CarComponent,则编译期生成的实现类为 DaggerCarComponent),我们通过调用这个实现类的方法完成注入;

Demo1
// 构造方法不带参数
class Demo1 @Inject constructor() {
    var value: String = "ValueFromDemo1"
    fun doSomething(): String {
        return "This is result"
    }
}

/**
 * 构造方法带参数
 * 但是所带参数的构造函数,也可以被@Inject修饰,即所带参数的类型的类也是自己写的
 */
class Demo2A @Inject constructor(val demo2B: Demo2B) {
    var value: String = "ValueFromDemo2A"
}
class Demo2B @Inject constructor(){
    var value:String ="ValueFromDemo2B"
}
DemoComponent
@Component
interface DemoComponent {
    // 注入进来 SampleHiltHomeActivity
    fun inject(activity: SampleHiltHomeActivity)
}
SampleActivity
    @Inject
    lateinit var mDemo1A: Demo1
    @Inject
    lateinit var mDemo2A: Demo2A
    @Inject
    lateinit var mDemo2B: Demo2B

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.sample_hilt_activity_home)
        DaggerDemoComponent.builder().build().inject(this)
    }
    
    override fun onClick(v: View?) {
        when (v?.id) {
            R.id.tv_dagger_demo1 -> {
                LogUtils.d(TAG, "demo1 = ${mDemo1A.value}")
                tvDisplay?.text = "demo1 = ${mDemo1A.value}"
            }
            R.id.tv_dagger_demo2 -> {
                LogUtils.d(TAG, "demo2A.value = ${mDemo2A.value}")
                LogUtils.d(TAG, "demo2B.demo2B.value = ${mDemo2A.demo2B.value}")
                LogUtils.d(TAG, "demo2B.value = ${mDemo2B.value}")
                tvDisplay?.text =
                    """
                    | demo2A.value = ${mDemo2A.value}
                    | 
                    | demo2B.demo2B.value = ${mDemo2A.demo2B.value}
                    | 
                    | demo2B.value = ${mDemo2B.value}
               """.trimMargin()
            }
Demo1 输出结果:
Log: demo1 = ValueFromDemo1
Demo2 输出结果:
Log: demo2A.value = ValueFromDemo2A
Log: demo2B.demo2B.value = ValueFromDemo2B
Log: demo2B.value = ValueFromDemo2B

2.2 @Module & @Provider:

2.2.1 场景1:

你要注入的实例的类型就不是你自己写的类,无法使用@inject

Demo3

有些实例类型,我无法掌控。 别慌!
@Module@Inject+@Component+@Module(含@Provides)来帮忙

@Module
class Demo3Module {
    @Provides
    fun provideDemo3(): String {
        return  "我是String,你能在我的构造方法上加@inject吗?是的,你不能!"
    }
}
DemoComponent
@Component(modules = [Demo3Module::class])
interface DemoComponent {
    // 注入进来 SampleHiltHomeActivity
    fun inject(activity: SampleHiltHomeActivity)
}
SampleActivity
    @Inject
    lateinit var mDemo3: String

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.sample_hilt_activity_home)
        DaggerDemoComponent.builder().build().inject(this)
    }
    
    override fun onClick(v: View?) {
        when (v?.id) {
            R.id.tv_dagger_demo3 -> {
                LogUtils.d(TAG, "demo3(String) = $mDemo3")
                tvDisplay?.text = "demo3(String) = $mDemo3"
            }
Demo3 输出结果
Log: demo3(String) = 我是String,你能在我的构造方法上加@inject吗?是的,你不能!

2.2.2 场景2:

你要注入的实例的类型就是你自己写的类,但是带了一个参数,这个参数类型的类不是你自己写的,无法使用@inject

Demo4
class Demo4 @Inject constructor(var number: Int) {
    var value: String = "ValueFromDemo4"
    fun doSomething(): String {
        return "This is result"
    }
}
Demo4Module
@Module
class Demo4Module {
    @Provides
    fun provideDemo4(): Int {
        return 1001001
    }
}
DemoComponent
@Component(modules = [Demo4Module::class])
interface DemoComponent {
    // 注入进来 SampleHiltHomeActivity
    fun inject(activity: SampleHiltHomeActivity)
}
SampleActivity
    @Inject
    lateinit var mDemo4: Demo4

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.sample_hilt_activity_home)
        DaggerDemoComponent.builder().build().inject(this)
    }

    override fun onClick(v: View?) {
        when (v?.id) {
            R.id.tv_dagger_demo4 -> {
                LogUtils.d(TAG, "demo4.value(Int) = ${mDemo4.number}")
                tvDisplay?.text = "demo4.value(Int) = ${mDemo4.number}"
            }
    

Demo4 输出结果:

Log: demo4.value(Int) = 1001001

2.3 @Qualifier

那么 Dagger2 怎么知道这个方法是为谁提供依赖呢?

答案就是返回值的类型,Dagger2 根据返回值的类型来决定为哪个被@Inject 标记了的变量赋值。

但是问题来了,一旦有多个一样的返回类型 Dagger2 就懵逼了。

@Qulifier 的存在正式为了解决这个问题,我们使用@Qulifier 来定义自己的注解,然通过自定义的注解去标注++提供依赖的方法++和++依赖需求方++(也就是被@Inject 标注的变量),这样 Dagger2 就知道为谁提供依赖了。

一个更为精简的定义:当类型不足以鉴别一个依赖的时候,我们就可以使用这个注解标示;

QualifierA
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class QualifierA()
QualifierB
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class QualifierB()
Demo5Module
@Module
class Demo5Module {

    @QualifierA
    @Provides
    fun provideDateA(): Date {
        return Date(System.currentTimeMillis()); //获取当前时间
    }

    @QualifierB
    @Provides
    fun provideDateB(): Date {
        return Date(System.currentTimeMillis() + 111111); //获取当前时间
    }
}
DemoComponent
@Component(modules = [Demo5Module::class])
interface DemoComponent {
    // 注入进来 SampleHiltHomeActivity
    fun inject(activity: SampleHiltHomeActivity)
}
SampleActivity
    @QualifierA
    @Inject
    lateinit var mDemo5A: Date // 随便找了一个系统类。无法在构造上添加@Inject

    @QualifierB
    @Inject
    lateinit var mDemo5B: Date

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.sample_hilt_activity_home)
        DaggerDemoComponent.builder().build().inject(this)
    }
    
    override fun onClick(v: View?) {
        when (v?.id) {
            R.id.tv_dagger_demo5 -> {
                val formatter = SimpleDateFormat("YYYY-MM-dd HH:mm:ss") //设置时间格式
                formatter.timeZone = TimeZone.getTimeZone("GMT+08"); //设置时区
                // 5A
                val date5A = formatter.format(mDemo5A) //格式转换
                LogUtils.d(TAG, "demo5A.value(Date) = $date5A")
                // 5B
                val date5B = formatter.format(mDemo5B) //格式转换
                LogUtils.d(TAG, "demo5B.value(Date) = $date5B")

                tvDisplay?.text =
                    """
                    | demo5A.value(Date) = $date5A
                    | 
                    | demo5B.value(Date) = $date5B
               """.trimMargin()
            }
Demo5 输出结果
Log: demo5A.value(Date) = 2022-11-24 16:20:47
Log: demo5B.value(Date) = 2022-11-24 16:22:38

2.4 @Named

除了上面的@Qualifier注解,还可以用@Named注解达到同样的效果

Demo6Module
@Module
class Demo6Module {

    @Provides
    @Named("DateC")
    fun provideDateC(): Date {
        return Date(System.currentTimeMillis() + 222222); //获取当前时间
    }

    @Provides
    @Named("DateD")
    fun provideDateD(): Date {
        return Date(System.currentTimeMillis() + 333333); //获取当前时间
    }
}
DemoComponent
@Component(modules = [Demo6Module::class])
interface DemoComponent {
    // 注入进来 SampleHiltHomeActivity
    fun inject(activity: SampleHiltHomeActivity)
}
SampleActivity
    @Inject
    @Named("DateC")
    lateinit var mDemo6A: Date

    @Inject
    @Named("DateD")
    lateinit var mDemo6B: Date

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.sample_hilt_activity_home)
        DaggerDemoComponent.builder().build().inject(this)
    }
    
    override fun onClick(v: View?) {
        when (v?.id) {
            R.id.tv_dagger_demo6 -> {
                // https://zhuanlan.zhihu.com/p/24454466
                val formatter = SimpleDateFormat("YYYY-MM-dd HH:mm:ss") //设置时间格式
                formatter.timeZone = TimeZone.getTimeZone("GMT+08"); //设置时区
                // 6A
                val date6A = formatter.format(mDemo6A) //格式转换
                LogUtils.d(TAG, "demo6A.value(Date) = $date6A")
                // 6B
                val date6B = formatter.format(mDemo6B) //格式转换
                LogUtils.d(TAG, "demo6B.value(Date) = $date6B")

                tvDisplay?.text =
                    """
                    | demo6A.value(Date) = $date6A
                    | 
                    | demo6B.value(Date) = $date6B
               """.trimMargin()
            }
Demo6 输出结果
Log: demo6A.value(Date) = 2022-11-24 16:24:29
Log: demo6B.value(Date) = 2022-11-24 16:26:20

2.5 @Singleton

@Singleton 其实就是一个通过@Scope 定义的注解,我们一般通过它来实现全局单例。

但实际上它并不能提前全局单例,是否能提供全局单例还要取决于对应的 Component 是否为一个全局对象。

Demo7
@Singleton
class Demo7 @Inject constructor() {

    var value: String = "ValueFromDemo7"
    var value1: String = "ValueFromDemo77"

    fun doSomething(): String {
        return "This is result"
    }
}
DemoComponent
@Singleton
@Component
interface DemoComponent {
    // 注入进来 SampleHiltHomeActivity
    fun inject(activity: SampleHiltHomeActivity)
}
SampleActivity
    @Inject
    lateinit var mDemo1A: Demo1
    @Inject
    lateinit var mDemo1B: Demo1
    @Inject
    lateinit var mDemo7A: Demo7
    @Inject
    lateinit var mDemo7B: Demo7

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.sample_hilt_activity_home)
        DaggerDemoComponent.builder().build().inject(this)
    }

    override fun onClick(v: View?) {
        when (v?.id) {
            R.id.tv_dagger_demo7 -> {
                LogUtils.d(TAG, "Demo1 是否是单例状态 -> ${mDemo1A === mDemo1B}")
                LogUtils.d(TAG, "Demo7 是否是单例状态 -> ${mDemo7A === mDemo7B}")
                tvDisplay?.text = """
                    |Demo1 是否是单例状态 ->   ${mDemo1A === mDemo1B}
                    |Demo7 是否是单例状态 ->   ${mDemo7A === mDemo7B}
                """.trimMargin()
            }
Demo7 输出结果:
Log: Demo1 是否是单例状态 -> false
Log: Demo7 是否是单例状态 -> true

@Scrope

(1) Scope 的作用,就是提供在当前 Component 实例 范围内的单例。
假设 DaggerDemoComponent 能够提供 Demo8 实例

DemoComponent 被自定义的 @UserScope 标注,那就意味着

一旦一个 DaggerDemoComponent 实例创建完成,

那么其调用 injectTo 方法,进行注入时,所有注入的 User 对象都是同一个实例

知道 DaggerDemoComponent 被重新创建,才会提供一个不一样的Demo8实例

Demo8
class Demo8(){}
@Demo8Scope
@Retention(AnnotationRetention.RUNTIME)
@Scope
annotation class Demo8Scope {}
Demo8Module
@Module
class Demo8Module {
    @Provides
    @Demo8Scope // 绑定 Demo8Scope
    fun provideDateE(): Demo8 {
        return Demo8()
    }
}
DemoComponent
@Demo8Scope // 绑定 Demo8Scope
@Component(modules = [Demo8Module::class])
interface DemoComponent {
    // 注入进来 SampleHiltHomeActivity
    fun inject(activity: SampleHiltHomeActivity)
}
SampleActivity
    @Inject
    lateinit var mDemo8A: Demo8
    @Inject
    lateinit var mDemo8B: Demo8

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.sample_hilt_activity_home)
        DaggerDemoComponent.builder().build().inject(this)
    }

    override fun onClick(v: View?) {
        when (v?.id) {
            R.id.tv_dagger_demo8 -> {
                LogUtils.d(TAG, "Demo8 是否是单例状态 -> ${mDemo8A === mDemo8B}")
                tvDisplay?.text = """
                    |Demo8 是否是单例状态 ->   ${mDemo7A === mDemo7B}
                """.trimMargin()
            }
Demo8 输出结果:
Log: Demo8 是否是单例状态 -> true

参考文档:

https://www.jianshu.com/p/fcd11506b85d
https://blog.csdn.net/weixin_45258969/article/details/94459713
https://blog.csdn.net/u010194271/article/details/114072887
https://blog.csdn.net/Android_Study_OK/article/details/90518342
https://blog.csdn.net/guolin_blog/article/details/109787732
https://zhuanlan.zhihu.com/p/423771913

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容