Dagger 2 ❤️ Android
前言
上篇文章介绍了Dagger 2
的基本使用,本文跟随官方文档实践一下Dagger 2
在Android
中的使用,可以看做是官方文档的不完全翻译。
本文有关Dagger 2
的使用分为Activity
和Fragment
两部分,二者的使用几乎没有差别,最后介绍一下在 Google 官方 Demo 中学到的一个小技巧,可以将几乎所有的和Dagger 2
的逻辑放到一份代码里面,对Android
工程的影响极小。
首先要添加相关依赖(Kotlin 环境):
apply plugin: 'kotlin-kapt'//引用该插件
implementation "com.google.dagger:dagger:$rootProject.dagger2Version"
implementation "com.google.dagger:dagger-android-support:$rootProject.dagger2Version"//Android 特需
kapt "com.google.dagger:dagger-compiler:$rootProject.dagger2Version"//注意如果是 kotlin 语言,这里需要时#kapt#
在 Activity 中的使用
Application 范围内的@Component
首先创建整个应用程序使用的@Component
,并将AndroidInjectionModule
加入其中,实现Inject
注入入口:
@Component(modules = [AndroidInjectionModule::class, MainActivityModule::class])
interface AppComponent {
fun inject(application: MainApplication)
}
AppComponent
的范围是整个应用程序都有效。
创建单个 Activity 的@Subcomponent
创建某个Activity
专属的@Subcomponent
,用于提供AndroidInjector.Builder
。
@Subcomponent
interface MainActivitySubComponent : AndroidInjector<MainActivity> {
@Subcomponent.Builder
abstract class Builder : AndroidInjector.Builder<MainActivity>()
}
创建单个 Activity 的@Module
创建属于整个Activity
的@Module
,注意这里要指明@subcomponents
为刚刚创建的MainActivitySubComponent
。
@Module(includes = [MainActivityModule.InnerModule::class], subcomponents = [MainActivitySubComponent::class])
class MainActivityModule {
@Provides
fun bindWaitForInjectClass() = WaitForInjectClass()
@Module
abstract class InnerModule {
@Binds
@IntoMap
@ClassKey(MainActivity::class)
abstract fun bindInjectorFactory(builder: MainActivitySubComponent.Builder): AndroidInjector.Factory<*>//注意这里是 Factory<*>
}
}
class WaitForInjectClass //一个供依赖注入的类
可以看到在MainActivityModule
中提供了一个方法利用刚刚MainActivitySubComponent
中提供的MainActivitySubComponent.Builder
实例生成了一个AndroidInjector.Factory
,而这个Factory
就是我们后面要将MainActivityModule
中的依赖实例通过AppComponent
传递给MainActivity
实例的关键。
此外还可以看到提供该 Factory 的方法是放到了另外一个抽象类里面然后再导入 MainActivityModule 中的,这是因为该方法的注解@Binds 要求方法是抽象的,而 MainActivityModule 要是需要给 Activity 提供依赖实例所必须的@Provides 又要求类不能是抽象的,否则就要求该方法是静态的。权衡之下我觉得这种方式是比较能接受的,当然也不排除有其他更优雅的解决方案,欢迎提Issue告知。
然后,将MainActivityModule
加入到应用程序的@Component
——AppComponent
中。
使 Application 继承自 HasActivityInjector
使当前MainApplication
继承自HasActivityInjector
,该接口只有一个方法:
/** Returns an {@link AndroidInjector} of {@link Activity}s. */
AndroidInjector<Activity> activityInjector();
这个类是用来为相应的Activity
提供一个AndroidInjector
。由于我们已经在AppComponent
中包括了AndroidInjectionModule
,所以Dagger 2
已经可以自动为我们注入DispatchingAndroidInjector
依赖,所以接下来的代码如下:
class MainApplication : HasActivityInjector, Application() {
override fun onCreate() {
super.onCreate()
DaggerAppComponent.create().inject(this)
}
@Inject
lateinit var dispatchActivityInjector: DispatchingAndroidInjector<Activity>
override fun activityInjector() = dispatchActivityInjector//返回 Dagger 2 为我们注入的 dispatchActivityInjector 对象
}
在onCreate()
方法中传入当前Application
的依赖。
在 Activity 中使用自动注入依赖
做完了以上所有内容,我们只需要在Activity
中添加如下代码就可以实现自动注入:
class MainActivity : AppCompatActivity() {
@Inject
lateinit var waitForInjectClass: WaitForInjectClass
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)//注意这里,在 super() 之前调用
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_dagger2_x_android_main)
Log.d("TAG", "The Class is $waitForInjectClass")
}
}
以上所有代码如下,或者也可以在这里找到:
这一切是怎么实现的呢?
在Android
程序运行时,AndroidInjection.inject()
从Application
中的activityInjector()
方法获取到 DispatchingAndroidInjector<Activity>
,然后将Activity
传入inject(Activity)
。
DispatchingAndroidInjector
通过AppComponent
找到我们在MainActivityModule
提供的对应的AndroidInjector.Factory
,然后创建了 AndroidInjector
——这就是我们当前Activity
对应的MainActivitySubComponent
。
接下来便按照之前的逻辑,从MainActivitySubComponent
中查找提供waitForInjectClass
的实例方法完成注入。
在 Fragment 中的使用
Dagger 2
在Fragment
的使用和在Activity
中的使用十分相似。
通过之前的代码我们可以知道,其基本的原理依旧是利用@Component
和@subcomponent
,@Module
之间的关联关系将Application
和Activity
等的依赖注入通过AndroidInjector
关联起来的:
MainActivitySubComponent
通过将MainActivityModule
加入到AppComponent
之中,然后当MainActivity
之中需要使用到MainActivitySubComponent
时,又通过AndroidInjector
从AppComponent
中拿到MainActivityModule
中的AndroidInjector.Factory
,通过该Factory
和MainActivitySubComponent
中的Builder
产生关联,从而获取到了MainActivitySubComponent
的实例供Activity
使用。
在Fragment
中我们也可以这样处理,只不过由于Fragment
的特性,他的@Module
不仅可以交给Application
的@Component
,也可以交给其他Fragment
或者Activity
的@Component
,让其实现HasFragmentInjector
即可,这取决于我们想要给Fragment
绑定的依赖。
具体的实现一般分为下面几步:
创建
Application
的@Component
并添加AndroidInjectionModule
创建实现了
AndroidInjector<MainFragment>
的MainFragmentSubComponent
,其内部有方法提供AndroidInjector.Builder<MainFragment>
创建包含了提供
AndroidInjector.Factory<*>
的抽象方法的MainFragmentModule
,指定其subcomponents
为MainFragmentSubComponent
;将
MainFragmentSubComponent
加入到想要加入的类的@Component
中,比如AppComponent
类在
Application
(如果上一步是Activity
,则本步也是Activity
等)中参照在Activity
实现的步骤实现HasFragmentInjector
上述完整的代码如下,或者也可以在这里找到:
关于Fragment
加入到Activity
的 Demo 在官方文档有,这里就不再赘述了,其实只要掌握原理,其他用法的完全可以触类旁通。
一个小技巧
通过观察上面的两份代码,我们发现虽然这Dagger 2
已经替我们做了好多事情,我们只需要在需要使用依赖注入的类中使用诸如AndroidInjection.inject(this)
这样的代码就可以了,但是如果Activity
、Fragment
类过多的时候,这样的重复性工作仍然是个不小的工作量,万一有某处遗忘了便会导致出错。
这时就可以用到我在Google 官方示例代码中学到的一个小技巧了 (针对本文中的例子做了一些修改),或者你也可以到这里查看源码: