Android--Dagger2入门

日常开发过程中,随着业务的增加,项目也越来越庞大,于是我们在项目中封装了很多类,并且在很多地方都要用到它们,有的类是单例,有的不是,当我们不得已需要修改这些类的生成代码时,工作量就特别大了,可谓是牵一发而动全身。因此,我们希望在用到对象的时候,不必关心它是如何生成的。这个思想就是IOC(控制反转),也就是依赖注入。Dagger也是一个IOC框架,对于大型项目,我们有必要去学习使用它

一、构造函数注入

dagger拥有两种方式注入对象,一种是利用@Inject注解构造函数

1.gradle中导入dagger依赖

在moudle的gradle中做如下配置,我在2.30.1版本中尝试使用构造函数注入,发现怎么编译都会报错,结果最后使用最新版本就可以了:

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-kapt'
}

dependencies {
    ...

    implementation "com.google.dagger:dagger:2.40.4"
    kapt "com.google.dagger:dagger-compiler:2.40.4"
}
2.自定义类,并对构造函数使用@Inject注解

这边定义两个类,分别代表本地和远程的数据源,并对构造函数使用@Inject注解

/**
 * 模拟本地数据源
 */
class LocalDataSource @Inject constructor()
/**
 * 模拟远程数据源
 */
class RemoteDataSource @Inject constructor()

定义包装类DataSource,包含上面两个类,同样对构造函数使用@Inject注解

/**
 * 数据源包装类
 * Created by aruba on 2021/12/4.
 */
data class DataSource @Inject constructor(
    var remoteDataSource: RemoteDataSource,
    var localDataSource: LocalDataSource
) 
3.使用@Component注解一个接口,表示一个注入中间件

对于需要注入的对象,dagger并不是直接注入,而是需要一个中间件去注入他们,使用代理模式的思想,这样的好处是方便管理和控制

/**
 * 注入中间件
 * Created by aruba on 2021/12/4.
 */
@Component
interface ApplicationComponent {
    /**
     * 表示注入到 MainActivity中
     */
    fun inject(activity: MainActivity)
}

build下项目后,dagger会对应生成一个DaggerApplicationComponent类,通过上篇文章,我们知道用的是APT技术

4.在需要使用的地方使用@Inject注解
class MainActivity : AppCompatActivity() {
    /**
     * 表示该对象需要自动注入
     */
    @Inject
    lateinit var dataSource: DataSource

...
5.在合适的地方,调用中间件注入

调用生成类DaggerApplicationComponent的注入方法

class MainActivity : AppCompatActivity() {

    /**
     * 表示该对象需要自动注入
     */
    @Inject
    lateinit var dataSource: DataSource

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

        Log.i("aruba_log", dataSource.toString())
    }
}

运行后日志:
2021-12-04 /com.aruba.daggerapplication I/aruba_log: DataSource(remoteDataSource=com.aruba.daggerapplication.di.datasource.RemoteDataSource@1ae5a6b, localDataSource=com.aruba.daggerapplication.di.datasource.LocalDataSource@8b49c8)

可以看到dagger帮我们自动生成注入对象了,并且我们在使用的地方不需要关注它是如何生成的


二、模块注入

第二种方式就是模块注入,构造函数注入是以类为对象,模块注入则是以方法为对象
接下来尝试使用网络请求,以获取百度的首页HTML

1.依赖网络框架
dependencies {
    ...

    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation "com.squareup.retrofit2:converter-scalars:2.9.0"
}

别忘了在Manifest.xml中添加权限

2.定义Retrofit API
interface BaiduApiService {
    @GET("/index.html")
    fun index(): Call<String>
}
3.使用@Module注解一个类,表示它是一个模块
/**
 * 表示一个网络模块
 * Created by aruba on 2021/12/4.
 */
@Module
class NetworkModule {

}
4.在module中使用@Provides注解方法,给Component提供获取Api的方法
@Module
class NetworkModule {

    @Provides
    fun getBaiduApiService(): BaiduApiService {
        return Retrofit.Builder()
            .baseUrl("https://www.baidu.com")
            .addConverterFactory(ScalarsConverterFactory.create())
            .build().create(BaiduApiService::class.java)
    }
}
5.Component中指定modules
/**
 * 注入中间件
 * Created by aruba on 2021/12/4.
 */
@Component(modules = [NetworkModule::class])
interface ApplicationComponent {
    /**
     * 表示注入到 MainActivity中
     */
    fun inject(activity: MainActivity)
}
6.在需要使用的地方使用@Inject注解
class MainActivity : AppCompatActivity() {

    /**
     * 表示该对象需要自动注入
     */
    @Inject
    lateinit var dataSource: DataSource

    /**
     * BaiduApiService注入
     */
    @Inject
    lateinit var baiduApiService: BaiduApiService

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

        Log.i("aruba_log", dataSource.toString())

        getIndex()
    }

    /**
     * 获取百度首页
     */
    private fun getIndex() {
        baiduApiService.index().enqueue(object : Callback<String> {
            override fun onResponse(call: Call<String>, response: Response<String>) {
                findViewById<TextView>(R.id.tv_hello).text = response.body()
            }

            override fun onFailure(call: Call<String>, t: Throwable) {
            }
        })
    }
}

效果:



模块注入同样也实现了自动注入对象,并且这种方式可读性和可维护性更高

三、使用作用域管理对象生命周期

通过上面两种方式,我们知道了如何注入对象,但是我们并不知道注入的对象的生命周期,有时我们希望获取的对象是一个单例,这种情况仅仅使用注入是无法实现的

下面例子,通过注入两个相同类型对象,查看它们是否是同一份实例
在MainActivity中同时注入两个DataSource对象,并通过打印日志,观测结果

class MainActivity : AppCompatActivity() {

    /**
     * 表示该对象需要自动注入
     */
    @Inject
    lateinit var dataSource1: DataSource

    @Inject
    lateinit var dataSource2: DataSource

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

        Log.i("aruba_log", dataSource1.toString())
        Log.i("aruba_log", dataSource2.toString())
        //判断是否是一个对象
        Log.i("aruba_log", "${dataSource2 === dataSource1}")
    }
}

日志打印:
2021-12-04/com.aruba.daggerapplication I/aruba_log: DataSource(remoteDataSource=com.aruba.daggerapplication.di.datasource.RemoteDataSource@137fdbe, localDataSource=com.aruba.daggerapplication.di.datasource.LocalDataSource@31b1e1f)
2021-12-04/com.aruba.daggerapplication I/aruba_log: DataSource(remoteDataSource=com.aruba.daggerapplication.di.datasource.RemoteDataSource@b3756c, localDataSource=com.aruba.daggerapplication.di.datasource.LocalDataSource@7b81735)
2021-12-04/com.aruba.daggerapplication I/aruba_log: false

结果显示这两个对象不是同一个实例

在使用构造注入或Module注入时,一旦使用了作用域注解,其Component也要使用相同的作用域注解,否则编译会报错。同一个Component实例在注入对象时,一旦发现注入方式使用了作用域,那么它们注入的对象将会是同一份实例

1.使用@Singleton注解实现注入相同实例

@Singleton注解为dagger默认提供的一个作用域注解。定义一个构造函数注入方式,并使用该注解

/**
 * 表示该注入的对象的作用域为Singleton
 * Created by aruba on 2021/12/4.
 */
@Singleton
class SingletonTest @Inject constructor() 

在Component中,使用相同作用域,并且我重新定义了一个ScopeActivity来测试结果

@Singleton
@Component(modules = [NetworkModule::class])
interface ApplicationComponent {
    /**
     * 表示注入到 MainActivity中
     */
    fun inject(activity: MainActivity)

    /**
     * 表示注入到 ScopeActivity中
     */
    fun inject(activity: ScopeActivity)
}

ScopeActivity中需要注入两个SingletonTest 对象,并打印是否为同一个实例

class ScopeActivity : AppCompatActivity() {

    @Inject
    lateinit var singleton1: SingletonTest

    @Inject
    lateinit var singleton2: SingletonTest

    override fun onCreate(savedInstanceState: Bundle?) {
        DaggerApplicationComponent.create().inject(this)
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_scope)

        Log.i("aruba_log", "singleton1 hashcode: ${singleton1.hashCode()}")
        Log.i("aruba_log", "singleton2 hashcode: ${singleton2.hashCode()}")
    }
}

日志结果:
2021-12-04/com.aruba.daggerapplication I/aruba_log: singleton1 hashcode: 246939604
2021-12-04/com.aruba.daggerapplication I/aruba_log: singleton2 hashcode: 246939604

结果显示,这两个对象是同一份实例

2.使用@Scope元注解自定义作用域
@Scope
@Retention(AnnotationRetention.SOURCE)
annotation class MyScope

将上面的作用域注解替换成MyScope

@MyScope
class SingletonTest @Inject constructor() 
@MyScope
@Component(modules = [NetworkModule::class])
interface ApplicationComponent {
    /**
     * 表示注入到 MainActivity中
     */
    fun inject(activity: MainActivity)

    /**
     * 表示注入到 ScopeActivity中
     */
    fun inject(activity: ScopeActivity)
}

日志结果:
2021-12-04/com.aruba.daggerapplication I/aruba_log: singleton1 hashcode: 246939604
2021-12-04/com.aruba.daggerapplication I/aruba_log: singleton2 hashcode: 246939604

和使用@Singleton是相同的效果

3.模块注入方式,使用作用域

模块注入方式,使用作用域注解在方法上:

@Module
class NetworkModule {

    @MyScope
    @Provides
    fun getBaiduApiService(): BaiduApiService {
        return Retrofit.Builder()
            .baseUrl("https://www.baidu.com")
            .addConverterFactory(ScalarsConverterFactory.create())
            .build().create(BaiduApiService::class.java)
    }
}

四、使用子组件实现多个作用域

即使用了同一个作用域,不同的Component实例进行注入,最后生成的对象还是不同的实例,即作用域管理的生命周期是跟随Component的。但一般情况下,我们一个APP只需要一份Component实例,而一个App中,往往有着不同的作用域

1.不同Component实例,作用域并不会生效

在MainActivity中,也定义注入一个SingleTest对象,注意每调用一次DaggerApplicationComponent.create(),会创新一个新的DaggerApplicationComponent对象

class MainActivity : AppCompatActivity() {

    /**
     * 表示该对象需要自动注入
     */
    @Inject
    lateinit var dataSource1: DataSource

    @Inject
    lateinit var dataSource2: DataSource

    /**
     * BaiduApiService注入
     */
    @Inject
    lateinit var baiduApiService: BaiduApiService

    @Inject
    lateinit var singleton: SingletonTest

    override fun onCreate(savedInstanceState: Bundle?) {
        // 每次create,创建一个新的DaggerApplicationComponent
        DaggerApplicationComponent.create().inject(this)
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        Log.i("aruba_log", "MainActivity singleton hashcode: ${singleton.hashCode()}")

        findViewById<TextView>(R.id.tv_hello).setOnClickListener {
            startActivity(Intent(this@MainActivity, ScopeActivity::class.java))
        }
    }
}

跳转到ScopeActivity后,我查看下打印结果:
2021-12-04/com.aruba.daggerapplication I/aruba_log: MainActivity singleton hashcode: 20446654
2021-12-04/com.aruba.daggerapplication I/aruba_log: singleton1 hashcode: 127836367
2021-12-04/com.aruba.daggerapplication I/aruba_log: singleton2 hashcode: 127836367

MainActivity和ScopeActivity中都调用了Component的create方法,所以两份Component实例注入的对象是不同的实例

2.子组件支持不同作用域

如果想要一个Component下使用不同的作用域,Component是不支持的,但Subcomponent可以使用,Subcomponent又可以被添加到Component中

2.1 定义新的作用域:SubScope
@Scope
@Retention(AnnotationRetention.SOURCE)
annotation class SubScope
2.2 定义注入对象,并指定作用域为SubScope
@SubScope
class SubObject @Inject constructor()
2.3 使用@Subcomponent注解定义子组件,并指定作用域为SubScope
@SubScope
@Subcomponent
interface SubComponent {
}
2.4 在子组件中使用@Subcomponent.Factory注解,定义获取该组件的接口

同时在子组件中提供注入方法,这边我新建了一个SubActivity

@SubScope
@Subcomponent
interface SubComponent {

    @Subcomponent.Factory
    interface Factory {
        fun create(): SubComponent
    }

    fun inject(activity: SubActivity)
}
2.5 提供给组件一个子组件module

在Moudle注解中,指定使用哪个子组件

@Module(subcomponents = [SubComponent::class])
class SubcomponentModule
2.6 Component中添加子组件moudle,并提供获取创建子组件的Factory的方法
@MyScope
@Component(modules = [NetworkModule::class, SubcomponentModule::class])
interface ApplicationComponent {
    /**
     * 表示注入到 MainActivity中
     */
    fun inject(activity: MainActivity)

    /**
     * 表示注入到 ScopeActivity中
     */
    fun inject(activity: ScopeActivity)

    /**
     * 提供获取创建子组件的Factory
     */
    fun subComponent(): SubComponent.Factory
}
2.7 定义一个全局的ApplicationComponent
object Component {
    val daggerApplicationComponent by lazy { DaggerApplicationComponent.create() }
}
2.8 测试结果

MainActivity中注入打印,并跳转到SubActivity

class MainActivity : AppCompatActivity() {

    /**
     * 表示该对象需要自动注入
     */
    @Inject
    lateinit var dataSource1: DataSource

    @Inject
    lateinit var dataSource2: DataSource

    /**
     * BaiduApiService注入
     */
    @Inject
    lateinit var baiduApiService: BaiduApiService

    @Inject
    lateinit var singleton: SingletonTest

    override fun onCreate(savedInstanceState: Bundle?) {
//        DaggerApplicationComponent.create().inject(this)
        Component.daggerApplicationComponent.inject(this)
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        Log.i("aruba_log", "MainActivity singleton hashcode: ${singleton.hashCode()}")

        findViewById<TextView>(R.id.tv_hello).setOnClickListener {
            startActivity(Intent(this@MainActivity, SubActivity::class.java))
        }
    }
}

SubActivity中创建子组件,并注入

class SubActivity : AppCompatActivity() {

    @Inject
    lateinit var singleton: SingletonTest

    @Inject
    lateinit var subObject1: SubObject

    @Inject
    lateinit var subObject2: SubObject

    override fun onCreate(savedInstanceState: Bundle?) {
        Component.daggerApplicationComponent.subComponent().create().inject(this)
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_sub)

        Log.i("aruba_log", "SubActivity singleton hashcode: ${singleton.hashCode()}")
        Log.i("aruba_log", "SubActivity subObject1 hashcode: ${subObject1.hashCode()}")
        Log.i("aruba_log", "SubActivity subObject2 hashcode: ${subObject2.hashCode()}")
    }
}

日志结果:
2021-12-04/com.aruba.daggerapplication I/aruba_log: MainActivity singleton hashcode: 257322958
2021-12-04/com.aruba.daggerapplication I/aruba_log: SubActivity singleton hashcode: 257322958
2021-12-04/com.aruba.daggerapplication I/aruba_log: SubActivity subObject1 hashcode: 219854405
2021-12-04/com.aruba.daggerapplication I/aruba_log: SubActivity subObject2 hashcode: 219854405

前两条证实了同一个父Component实例,就算使用子组件注入,作用域也有作用,后面两条说明我们成功的注入了子组件生成的对象

最后,附上一张dagger的结构图:


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

推荐阅读更多精彩内容

  • 1.Dagger2简介 1.1 Dagger2的描述 Github地址:Dagger2 Dagger2官网上介绍是...
    橙子只涩不酸阅读 795评论 0 1
  • 对于 Java 注解不熟悉 这一部分的开发者基础知识确实薄弱,那怎么办呢?当然是学习了。就算不为 Dagger2,...
    as_pixar阅读 607评论 1 2
  • 1.什么是dagger2 简单来说,dagger2是一个依赖注入框架,代替你生成实例,管理各种实例的依赖关系的库。...
    碧海鱼龙阅读 941评论 0 0
  • 一、首先你要知道什么是依赖? 想要理解Dagger2,首先你要理解一个概念,就是什么是依赖,懂的同学可以省过此段。...
    为梦想战斗阅读 442评论 0 0
  • 框架源码:https://github.com/google/daggerDagger 2 完全解析系列:Dagg...
    WaterYuan阅读 727评论 0 1