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: 20446654
2021-12-04/com.aruba.daggerapplication I/aruba_log: SubActivity singleton hashcode: 127836367
2021-12-04/com.aruba.daggerapplication I/aruba_log: SubActivity subObject1 hashcode: 44284508
2021-12-04/com.aruba.daggerapplication I/aruba_log: SubActivity subObject2 hashcode: 27693413

前两条再次证实了不同Component实例,作用域不起作用,后面两条说明我们成功的注入了子组件生成的对象

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

Demo地址:https://gitee.com/aruba/dagger-application.git
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值