Dagger2 基本使用
目录:
- 基本使用
- 添加 Module 使用
- Component 依赖
- Scope作用域——Singleton 单例
导入依赖
导入 Dagger2 直接参看github,Kotlin 中导入 Dagger 依赖,需要将 annotationProcessor 更改为 kapt,同时添加 kotlin-kapt 插件:
apply plugin: 'kotlin-kapt'
// ...
dependencies {
// ...
implementation"org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
implementation 'com.google.dagger:dagger:2.15'
kapt 'com.google.dagger:dagger-compiler:2.15'
}
基本使用
直接看栗子,简单的实现依赖注入创建 Product 实例。
首先创建一个 POJO 类 Product,并使用 @Inject 注解 Product 的无参构造方法。
class Product @Inject constructor()
然后创建一个以 @Component 注解标注 Component 接口, 并且添加一个无参的, 带参数的方法(方法名通过习惯命名为 inject)。
@Component
interface MainActivityComponent {
fun inject(mainActivity: MainActivity)
}
创建完成 Component 接口以后,需要手动 build 一下项目, 然后 Dagger 框架将会自动生成一个名字以 Dagger 开头的 Component 接口为后缀的实现类。这里为 DaggerMainActivityComponent。
最后在MainActivity 类的 onCreate() 方法中, 将 MainActivity 注入到 Component 中.
class MainActivity : AppCompatActivity() {
@Inject
lateinit var product: Product
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 两种注入方式
DaggerMainActivityComponent.create().inject(this)
// DaggerMainActivityComponent.builder().build().inject(this)
Log.i("MainActivity", product.toString())
}
}
这样,我们 Dagger 的基本依赖注入的实现就完成了。
上面实现的是 Product 类的构造方法不带参数的注入,接下来看看实现构造方法带参数的注入。
再创建一个 POJO 类 Factory,该类创建一个需要 Product 实例的构造方法。
class Factory @Inject constructor(val product: Product)
MainActivityComponent 接口不需要改变,修改 MainActivity 如下:
class MainActivity : AppCompatActivity() {
@Inject
lateinit var factory: Factory
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 两种注入方式
DaggerMainActivityComponent.create().inject(this)
// DaggerMainActivityComponent.builder().build().inject(this)
Log.i("MainActivity", "product: ${factory.product}, factory: $factory")
}
}
总结一下:
1. 将 MainActivity 注入到 Component 中.
2. 查找 MainActivity 是否存在被 Inject 注解的变量, 找到 factory 变量.
3. 准备实例化 Factory, 然后在 Factory 类中查找被 Inject 注解的构造方法.
4. 找到被注解的构造方法后, 调用该构造方法时, 发现需要 Product 实例才能调用构造方法.
5. Dagger 框架又去 Product 类中去查找被 Inject 注解的构造方法, 然后创建一个 Product 实例.
6. 最后将 Product 的实例交给 Factory 去创建 factory 实例.
添加 Module 使用
使用 @Inject 注入的时候,不是无所不能的,比如对于第三方框架,我们是不能去为其构造方法添加 @Inject 注解的,也不能构造接口。
那么这个时候,我就需要使用 @Module 注解了,那么接下来还是通过一个栗子来说明其用法。
首先模拟两个第三方类 OkHttpClient 和 RetrofitManager, 表示我们不可更改的类。
// OkHttpClient.kt
class OkHttpClient {
var cacheSize: Int = 0
}
// RetrofitManager.kt
class RetrofitManger(val client: OkHttpClient)
然后创建一个 Module 类,在其内创建一个被 @Provides 注解的,并且返回值为 OkHttpClient 类型的无参方法。同样的,还可以创建一个返回值为 RetrofitManager 类型,但是有参方法,参数为 OkHttpClient 实例。
@Module
class HttpActivityModule {
// 方法名无关紧要, 主要是返回值
@Provides
fun provideHttpClient(): OkHttpClient {
Log.i("HttpActivity", "HttpActivityModule...")
return OkHttpClient()
}
@Provides
fun provideRetrofitManager(client: OkHttpClient): RetrofitManger {
return RetrofitManger(client)
}
}
然后创建 Component,将 Module 类添加到注解 @Component 的 modules 属性上,其作用是将 Module 类注册到 Component 类中,表示在进行依赖注入时,Component 首先去该 Module 中查找提供依赖注入的具体对象。
@Component(modules = [HttpActivityModule::class])
interface HttpActivityComponent {
fun inject(httpActivity: HttpActivity)
}
最后,将 HttpActivity 注入到 Component 接口实现类中,HttpActivity 的代码如下:
class HttpActivity : AppCompatActivity() {
@Inject
lateinit var retrofitManger: RetrofitManger
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_http)
// Module 无构造参数时, 注入的写法可以有 3 种.
// DaggerHttpActivityComponent.create().inject(this)
// DaggerHttpActivityComponent.builder().build().inject(this)
DaggerHttpActivityComponent.builder()
.httpActivityModule(HttpActivityModule())
.build().inject(this)
Log.i("HttpActivity", "OkHttpClient: ${retrofitManger.client}, " +
"RetrofitManager: $retrofitManger")
}
}
这样就实现了第三方类的依赖注入。但是如果我们需要传入一些配置去构造第三方类的时候,我们可以通过使用 Module 的构造参数传入。
对 HttpActivityModule 进行修改,添加 Module 的构造方法,为第三方类 OkHttpClient 添加一个 缓存大小 的配置参数。
@Module
class HttpActivityModule(private val cacheSize: Int) {
@Provides
fun provideHttpClient(): OkHttpClient {
Log.i("HttpActivity", "HttpActivityModule...")
val client = OkHttpClient()
client.cacheSize = this.cacheSize
return client
}
@Provides
fun provideRetrofitManager(client: OkHttpClient): RetrofitManger {
return RetrofitManger(client)
}
}
这样在 HttpActivity 注入的时候,就需要传入一个 缓存大小 的实参了。
class HttpActivity : AppCompatActivity() {
@Inject
lateinit var retrofitManger: RetrofitManger
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_http)
// Module 有构造参数时,只有这种写法
DaggerHttpActivityComponent.builder()
.httpActivityModule(HttpActivityModule(100))
.build().inject(this)
Log.i("HttpActivity", "OkHttpClient: ${retrofitManger.client}, " +
"RetrofitManager: $retrofitManger")
}
}
总结:
1. 将 HttpActivity 注入到 Component 中.
2. 查找 HttpActivity 是否存在被 Inject 注解的变量, 找到 retrofitManger 变量.
3. 准备实例化 RetrofitManger, 这个时候因为在 Component 应用了 Module.
4. 所以会先去 Module 中查找被 Provides 注解, 并且返回值为 RetrofitManger 的方法.
5. 如果有查找到该方法, 但发现该方法需要 OkHttpClient 参数.
5.1 将继续在 Module 中查找被 Provides 注解, 并且返回值为 OkHttpClient 的方法.
5.2 如果查找到该方法, 那么直接调用该方法实例化 OkHttpClient.
5.3 最后将 OkHttpClient 的实例交给 步骤5 中找到的方法, 实例化 retrofitManger 对象, 结束.
5.4 如果没有找到该该方法, 将去 OkHttpClient 类中查找被 Inject 注解了构造方法.
5.5 找到构造方法之后, 跳转到 步骤 5.3, 结束.
6. 如果在 Module 中没有找到, 那么将会去 RetrofitManger 类中查找被 Inject 注解了构造方法.
7. 找到构造方法之后, 跳转到 步骤 5.
Component 依赖
当两个 Component 都可以实现同样的功能,那么我们就没必要再去重复写一遍该功能,这个时候我们可以使用 Component 依赖来解决这个问题,类似继承的作用。
Component 依赖可以有两种写法:
a. 使用注解 @Component 中的 dependence 属性
b. 使用 @Subcomponent 注解
a. 使用注解 @Component 中的 dependence 属性
这里也直接举个栗子来说明其用法,我们实现在 Activity 和 Fragment 中都依赖注入 Hello 对象。
首先创建一个 Hello 类.
class Hello
然后创建 Module 类,在其内提供 Hello 实例的依赖注入方法。
@Module
class HelloActivityModule {
@Provides
fun provideHello(): Hello {
return Hello()
}
}
创建父 Component 接口 HelloActivityComponent,在其内注册 Module,添加需要注入的目标位置方法 inject(target),并且还需暴露给子 Component 共享的依赖注入方法 provideHello()。
@Component(modules = [HelloActivityModule::class])
interface HelloActivityComponent {
// 需要注入的位置, 这里为 HelloActivity.
fun inject(helloActivity: HelloActivity)
// 暴露给子 Component 使用, 名字不必须和 Module 中相同,
// 但返回值必须是子 Component 需要的依赖注入对象.
fun provideHello(): Hello
}
然后在 HttpActivity 添加依赖注入的变量 hello,并且提供父 Component 实例给子 Component 使用,代码如下:
class HelloActivity : AppCompatActivity() {
@Inject
lateinit var hello: Hello
lateinit var helloActivityComponent: HelloActivityComponent
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_hello)
helloActivityComponent = DaggerHelloActivityComponent.create()
helloActivityComponent.inject(this)
supportFragmentManager.beginTransaction().add(R.id.fl_hello, HelloFragment()).commit()
Log.i("HelloActivity", hello.toString())
}
}
接着创建子 Component 接口 HelloFragmentComponent,该 Component 通过 dependencies 属性依赖于 HelloActivityComponent,代码如下:
@Component(dependencies = [HelloActivityComponent::class])
interface HelloFragmentComponent {
fun inject(fragment: HelloFragment)
}
最后在 HelloFragment 类中,获取父 Component 实现类,并且将父 Component 实现类设置到子 Component 实现类中,已达到需要注入 hello 对象时,通过父 Component 实现类调用 provideHello() 方法, 获取到共享 Module 实现类去获取 hello 实例的效果。
class HelloFragment : Fragment() {
@Inject
lateinit var hello: Hello
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_hello, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 获取父 Component 实现类.
val helloActivityComponent = (context as HelloActivity).helloActivityComponent
// 将父 Component 实现类添加到子 Component 实现类中, 以间接的获取 hello 实例.
DaggerHelloFragmentComponent.builder()
.helloActivityComponent(helloActivityComponent)
.build().inject(this)
Log.i("HelloFragment", hello.toString())
}
}
最后运行项目,将在 HelloActivity 和 HelloFragment 都能创建两个不同的 Hello 实例并打印出来。
需要注意点:
1、父 Component 中要显式的写出需要暴露可提供给子 Component 的方法;
2、子 Component 在注解中使用 dependencies= 来连接父 Component;
3、注意子 Component 实例化方式。
b. 使用 @Subcomponent 注解
将上面的栗子改造一下,新创建一个父 Component 接口 SubHelloActivityComponent 和子 Component 接口 SubHelloFragmentComponent.
首先定义子 Component 接口,以注解 @Subcomponent 标注。但是通过 @Subcomponent 注解标注的接口 Dagger 框架不会为该 Component 自动生成实现类,当在其父 Component 中被定义时,将会以内部类的形式实现该子 Component 接口:
// SubHelloFragmentComponent.kt
@Subcomponent
interface SubHelloFragmentComponent {
fun inject(helloFragment: HelloFragment)
}
然后定义父 Component 接口,在其内声明有 @Subcomponent 注解的 Component 作用返回值的方法(即声明获取子 Component 的方法)。
// SubHelloActivityComponent.kt
@Component(modules = [HelloActivityModule::class])
interface SubHelloActivityComponent {
fun inject(helloActivity: HelloActivity)
// 定义返回值为子 Component 的方法。在 Dagger 框架为该 Component
// 创建实现类 DaggerSubHelloActivityComponent 时, 将会同时为
// 其子 Component 接口以内部类的形式创建实例类并返回。
fun subHelloFragmentComponent(): SubHelloFragmentComponent
}
HelloActivity 将改为:
class HelloActivity : AppCompatActivity() {
@Inject
lateinit var hello: Hello
lateinit var subHelloActivityComponent: SubHelloActivityComponent
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_hello)
subHelloActivityComponent = DaggerSubHelloActivityComponent.create()
subHelloActivityComponent.inject(this)
supportFragmentManager.beginTransaction().add(R.id.fl_hello, HelloFragment()).commit()
Log.i("HelloActivity", hello.toString())
}
}
HelloFragment 将改为:
class HelloFragment : Fragment() {
@Inject
lateinit var hello: Hello
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_hello, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 通过父 Component 获取子 Component
(context as HelloActivity).subHelloActivityComponent
.subHelloFragmentComponent().inject(this)
Log.i("HelloFragment", hello.toString())
}
}
实现的效果和使用 dependencies 一样。
使用 @Subcomponent 注解需要注意点的:
1、先定义子 Component,使用 @Subcomponent 标注(不可同时再使用 @Component);
2、父 Component 中定义获得子 Component 的方法;
3、注意子 Component 实例化方式。
添加配置(参数)
上面实现的是直接在父 Component 中提供了返回子 Component 的方法,忽略了子 Component 构建时需要传入参数的情况,当然不需要传入参数也可以用这种方法;[重点:子 Component 构建时传入参数的话就需要在子 Component 中使用 @Subcomponent.Builder 注解(接口或抽象类)去添加]。
那么还等什么,直接上代码。
创建 POJO 类 AppBean 和 ActivityBean.
class AppBean
class ActivityBean
然后创建 Module 类 AppModule 和 MainActivityModule.
@Module
class AppModule {
// 提供 AppBean 实例的依赖注入
@Provides
fun provideAppBean(): AppBean {
return AppBean()
}
}
@Module
class MainActivityModule(private val activityBean: ActivityBean) {
// // 提供 ActivityBean 实例的依赖注入
@Provides
fun provideActivityBean(): ActivityBean {
return activityBean
}
}
接着创建 Component 接口 APPComponent 和 MainActivityComponent.
@Component(modules = [AppModule::class])
interface AppComponent {
// 添加返回值为被 @Subcomponent 注解的子 Component 类型的方法
fun mainBuilder(): MainActivityComponent.MainBuilder
}
@Subcomponent(modules = [MainActivityModule::class])
interface MainActivityComponent {
fun inject(mainActivity: MainActivity)
// 注意, 这是 @Subcomponent.
// 通过 @Subcomponent.Builder 注解创建一个 Builder 接口/抽象类,
// 为其注册的 Module 添加配置参数.
@Subcomponent.Builder
interface MainBuilder {
fun mainActivityModule(module: MainActivityModule): MainBuilder
fun build(): MainActivityComponent
}
}
接着在 Application 中创建父 Component 接口 AppComponent 实例。
// 需要将其配置到清单文件中.
class App : Application() {
lateinit var appComponent: AppComponent
override fun onCreate() {
super.onCreate()
appComponent = DaggerAppComponent.create()
}
}
最后在 MainActivity 中对 AppBean 和 ActivityBean 进行依赖注入:
class MainActivity : AppCompatActivity() {
@Inject lateinit var appBean: AppBean
@Inject lateinit var activityBean: ActivityBean
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
// 获取 DaggerAppComponent 实现类实例, 并获取其子 Component 的 Builder 实例,
// 通过该 Builder 实例为其注册的 Module 添加配置参数.
(application as App).appComponent.mainBuilder()
.mainActivityModule(MainActivityModule(ActivityBean()))
.build()
.inject(this)
Log.e("MainActivity", "appBean: $appBean, activityBean: $activityBean")
}
}
Scope作用域——Singleton 单例
如果需要同一个 Component 注入的变量为单例,需要为其 依赖注入类 和 Component 接口都添加 @Singleton 注解。
首先看看没有使用 @Singleton 注解的栗子:
创建 依赖注入类 Book 和 Component 接口 BookActivityComponent.
class Book @Inject constructor()
@Component
interface BookActivityComponent {
fun inject(bookActivity: BookActivity)
}
最后在 BookActivity 对 book1 和 book2 进行依赖注入,打印出来查看是否为同一个实例。
class BookActivity : AppCompatActivity() {
@Inject lateinit var book1: Book
@Inject lateinit var book2: Book
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_book)
DaggerBookActivityComponent.create().inject(this)
Log.i("BookActivity", "book1: $book1, book2: $book2")
}
}
运行程序,结果如下:
I/BookActivity: book1: com.np.daggerproject.singleton.Book@8d8f8d2, book2: com.np.daggerproject.singleton.Book@49e1da3
有运行结果可得,book1 和 book2 是两个不同的实例.
接下来,我们为 Book 类和 Component 接口添加 @Singleton 注解。
@Singleton
class Book @Inject constructor()
@Singleton
@Component
interface BookActivityComponent {
fun inject(bookActivity: BookActivity)
}
BookActivity 代码不变,运行程序,结果如下:
I/BookActivity: book1: com.np.daggerproject.singleton.Book@8d8f8d2, book2: com.np.daggerproject.singleton.Book@8d8f8d2
可以看出,book1 和 book2 为同一个对象。
但是,这样只能使在同一个注入目标类中的 Book 对象保持单例, 也就是说该单例范围只在该注入目标类中(如在 BookActivity 中保持单例, 但是在 Book2Activity 中就不能保持单例了)。
那么怎样才能使依赖注入对象 Book 全局保持单例呢。这个时候需要将其 Component 接口在 Application 中初始化, 然后在 BookActivity 中获取该 Component 实例注入 BookActivity,在 KotlinBookActivity 中也获取同一个 Component 实例注入 KotlinBookActivity,这样他们获取的 Book 对象将会是同一个。
将 BookActivityComponent 接口在 Application 中实例化。
class App : Application() {
lateinit var bookActivityComponent: BookActivityComponent
override fun onCreate() {
super.onCreate()
bookActivityComponent = DaggerBookActivityComponent.create()
}
}
然后在 BookActivity 中获取该 Component 实例。
class BookActivity : AppCompatActivity() {
@Inject lateinit var book1: Book
@Inject lateinit var book2: Book
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_book)
(application as App).bookActivityComponent.inject(this)
Log.e("BookActivity", "book1: $book1, book2: $book2")
button.setOnClickListener { startActivity(
Intent(this, KotlinBookActivity::class.java)) }
}
}
再在另一个 Activity 类 KotlinBookActivity 中也做同样的操作。
// 在 BookActivityComponent 添加一个 KotlinBookActivity 类为注入目标的方法
@Singleton
@Component
interface BookActivityComponent {
fun inject(bookActivity: BookActivity)
fun inject(kotlinActivity: KotlinBookActivity)
}
// KotlinBookActivity.kt
class KotlinBookActivity : AppCompatActivity() {
@Inject lateinit var book3: Book
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_kotlin_book)
(application as App).bookActivityComponent.inject(this)
Log.e("BookActivity", "book3: $book3")
}
}
最后运行程序,然后点击按钮跳转到 KotlinBookActivity 界面,结果如下:
E/BookActivity: book1: com.np.daggerproject.singleton.Book@42817ef, book2: com.np.daggerproject.singleton.Book@42817ef
E/BookActivity: book3: com.np.daggerproject.singleton.Book@42817ef
从结果可以看出,不同的注入目标中的依赖注入对象 Book 为同一个实例。