日常开发过程中,随着业务的增加,项目也越来越庞大,于是我们在项目中封装了很多类,并且在很多地方都要用到它们,有的类是单例,有的不是,当我们不得已需要修改这些类的生成代码时,工作量就特别大了,可谓是牵一发而动全身。因此,我们希望在用到对象的时候,不必关心它是如何生成的。这个思想就是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的结构图: