文章目录
一、Dagger是什么?
Dagger 会自动生成代码,该代码与您原本需要手动编写的代码相似。由于该代码是在编译时生成的,因此具有可追溯性,而且性能要高于其他基于反射的解决方案。
优点:
- 代码复用
- 易维护
- 易测试
还需要注意一点,Dagger不只属于Android,它是Java语言范畴的框架。
二、Dagger的使用
2.1 实现目标
本节实现的使用目标如上图所示。上图中的“箭头”代表依赖关系。上面的图在Dagger中被称为“Application Graph”。而描述“Application Graph”的方式就是使用注解了。
2.2 准备工作
我们假设已经实现了一个MVVM的demo,我们来将其改为Dagger的形式完成依赖关系。那么我们为什么要使用Dagger呢?在大型项目中,我们手写依赖,可能会出现手写错误,也可能会把控不好对象的声明周期,从而导致内存泄露,而Dagger可以规避这一点,它可以通过ApplicationGraph将依赖对象注入。
2.3 添加Dagger依赖
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
...
dependencies {
...
def dagger_version = "2.27"
implementation "com.google.dagger:dagger:$dagger_version"
kapt "com.google.dagger:dagger-compiler:$dagger_version"
}
2.4 @Inject注解的双层含义
当我们把@Inject注解在构造函数上,是告诉Dagger,如果去生成该对象;但是当将@Inject注解在成员变量上,是希望Dagger能够生成该对象。
我们首先要重构的是注册逻辑(将注册逻辑替换为Dagger)。
// @Inject 告诉Dagger如何提供RegistrationViewModel对象
// Dagger也知道RegistrationViewModel依赖了UserManager
class RegistrationViewModel @Inject constructor(val userManager: UserManager) {
...
}
//所以我们也要告诉Dagger如何获取UserManager对象
//那个Storage对象如何拿到呢?我们稍后再说(参考2.6节哦)
class UserManager @Inject constructor(private val storage: Storage) {
}
接下来我们需要提供RegistrationViewModel给RegistrationActivity:
class RegistrationActivity : AppCompatActivity() {
// @Inject 该字段将由Dagger提供实例
@Inject
lateinit var registrationViewModel: RegistrationViewModel
override fun onCreate(savedInstanceState: Bundle?) {
...
}
}
目前我们已经使用@Inject注解,将我我们的意图描述了出来。但是还需要一个“触发点”告诉Dagger来为RegistrationActivity对象生成“依赖对象图”。没错,这个触发点的实现是有@Component注解来实现的。
2.5 @Component注解
当我们想要dagger生成项目的依赖图时,我们需要使用@Component注解一个Interface。Dagger会生成一个容器来管理这些依赖对象。@Component注解Interface也会使得Dagger针对interface方法暴露的接口参数,生成依赖对象,来满足依赖关系。
@Component
interface AppComponent {
//Dagger将会为 RegistrationActivity对象生成依赖对象,满足依赖关系
fun inject(activity: RegistrationActivity)
}
一个@Component 接口会提供Dagger需要的信息在编译期生成依赖图。接口方法的参数将定义哪些类需要请求注入。
2.6 @Model @Bind 和 @BindInstance 注解
一个Dagger Model是一个使用@Model注解的类。它可以告诉Dagger如何提供某个类型的实例。比如2.4中的Storage,它是一个接口,无法实例化。就需要Model来提供如何具体类型的Storage。指明具体的类型需要@Bind注解或者@Provide注解。
//告诉Dagger 这是一个Dagger Model
@Module
class StorageModule {
//当Storage type被需要时,让 Dagger提供SharedPreferencesStorage实例
@Binds
abstract fun provideStorage(storage: SharedPreferencesStorage): Storage
}
//注意context对象该如何获取呢
class SharedPreferencesStorage @Inject constructor(context: Context) : Storage {
}
接下来将StorageModule加入到依赖图中
@Component(modules = [StorageModule::class])
interface AppComponent {
fun inject(activity: RegistrationActivity)
}
到目前为止,编译项目,是编译不过去的。因为SharedPreferencesStorage所依赖的context实例 对于Dagger来说是未知。我们知道context对象是有系统实例化的。因此我们需要在构建图是显示传给Dagger。这是@BindInstance就有用武之地了。(因为需要在AppComponent初始化时传入,因此需要修改AppComponent接口)
@Component
interface AppComponent {
//Dagger将会为 RegistrationActivity对象生成依赖对象,满足依赖关系
fun inject(activity: RegistrationActivity)
@Component.Factory
interface Factory{
//使用@BindInstance注解,被传入的context对象,在AppComponent中是可以获取到的
fun create(@BindInstance context:Context):AppComponent
}
}
2.7 注入到Activity中
open class MyApplication : Application() {
//创建一个全局属性,供应用的Activity调用
val appComponent: AppComponent by lazy {
DaggerAppComponent.factory().create(applicationContext)
}
open val userManager by lazy {
UserManager(SharedPreferencesStorage(this))
}
}
//注册页Activity
class RegistrationActivity : AppCompatActivity() {
// 该字段有Dagger提供值
@Inject lateinit var registrationViewModel: RegistrationViewModel
override fun onCreate(savedInstanceState: Bundle?) {
//请求Dagger注入当前对象所需要的依赖
(application as MyApplication).appComponent.inject(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_registration)
supportFragmentManager.beginTransaction()
.add(R.id.fragment_holder, EnterDetailsFragment())
.commit()
}
...
}
接下来我们还可以将MainActivity的逻辑改成Dagger注入的方式。
2.8 Scope
我们使用Scope为一个Component对象限定一个类型,保证该类型在该Component对象中,保持唯一。例如:我们想要是UserManager的类型在AppComponent中每次获取的对象都是同一个。我们可以使用如下的代码实现:
@Singleton
@Component(modules = [StorageModule::class])
interface AppComponent { ... }
@Singleton
class UserManager @Inject constructor(private val storage: Storage) {
...
}
由于RegisterationActivity和MainActivity使用的是同一个AppComponent对象,那么当他们获取UserManager对象时,会拿到同一个对象。
2.9 Subcomponents
在Demo中,注册页面RegisterationActivity是由两个Fragment组成,并且我们需要在两个Fragment和Activity之间共享同一个RegisterationModel。通过将其声明为@SingleInstance显然是不合理的。因为注册逻辑一点完成,RegisterationModel是没有存在的必要的。因此我们可以使用Subcomponents来Scope RegisterationModel对象。
Subcomponents能够使用其父Component图中对象
@Subcomponent
interface RegistrationComponent {
// Factory to create instances of RegistrationComponent
@Subcomponent.Factory
interface Factory {
fun create(): RegistrationComponent
}
fun inject(activity: RegistrationActivity)
fun inject(fragment: EnterDetailsFragment)
fun inject(fragment: TermsAndConditionsFragment)
}
@Singleton
@Component(modules = [StorageModule::class])
interface AppComponent {
@Component.Factory
interface Factory {
fun create(@BindsInstance context: Context): AppComponent
}
//暴露图中的RegistrationComponent.Factory
fun registrationComponent(): RegistrationComponent.Factory
fun inject(activity: MainActivity)
}
关联RegistrationComponent到AppComponent
@Module(subcomponents = [RegistrationComponent::class])
class AppSubcomponents
@Singleton
@Component(modules = [StorageModule::class, AppSubcomponents::class])
interface AppComponent { ... }
现在的依赖关系如下图所示:
2.10 Scope 子组件
接下来我们约束RegisterVM声明周期。我们将其捆绑到RegisterComponent上。
首先自定义一个Scope ,其作用等同于上文提到的@Singleton。
由于其作用于为Activity级别,故将其命名为ActivityScope
@Scope
@MustBeDocumented
@Retention(value = AnnotationRetention.RUNTIME)
annotation class ActivityScope
在RegistrationComponent使用ActivityScope,这样保证RegistrationComponent图中,所使用的的依赖对象如果被ActivityScope标注,则对象使用唯一。
本文中RegistrationViewModel对象就需要使用该注解
@ActivityScope
class RegistrationViewModel @Inject constructor(val userManager: UserManager) {
...
}
接下来是使用该子组件
class RegistrationActivity : AppCompatActivity() {
...
override fun onCreate(savedInstanceState: Bundle?) {
//创建该实例对象,便于其Fragment使用
registrationComponent = (application as MyApplication).appComponent.registrationComponent().create()
//生成依赖图
registrationComponent.inject(this)
super.onCreate(savedInstanceState)
...
}
...
}
class EnterDetailsFragment : Fragment() {
...
override fun onAttach(context: Context) {
super.onAttach(context)
//在Fragment中使用,需要在onAttach中调用,获取依赖对象
(activity as RegistrationActivity).registrationComponent.inject(this)
}
...
}
按照同样的思路,可以重构登录流程
2.11 约束多个Activity的声明周期
在Demo中,还有一种对象,需要约束声明周期,那就是登录之后的UserManager对象。它要保证登录之后,该对象是唯一的。但是该对象是为MainActivity和SettingActivity服务。因此我们需要再定义一个Scope — LoginUserScope
定义
@Scope
@MustBeDocumented
@Retention(value = AnnotationRetention.RUNTIME)
annotation class LoggedUserScope
并定义一个Component
@LoggedUserScope
@Subcomponent
interface UserComponent {
@Subcomponent.Factory
interface Factory {
fun create(): UserComponent
}
fun inject(activity: MainActivity)
fun inject(activity: SettingsActivity)
}
关联UserComponent到AppComponent的module中
@Module(subcomponents = [RegistrationComponent::class, LoginComponent::class, UserComponent::class])
class AppSubcomponents
@Singleton
@Component(modules = [StorageModule::class, AppSubcomponents::class])
interface AppComponent {
...
//暴露UserManager给MainActivity和SettingsActivity访问
fun userManager(): UserManager
...
}
为什么没有暴露UserComponent呢?因为我们将其放在了UserManager中
@Singleton
// 由于UserManager负责管理UserComponent的生命周期,它需要知道如何创建该对象,因此将factory传入
class UserManager @Inject constructor(private val userComponentFactory: UserComponent.Factory) {
// Add or edit the following lines
var userComponent: UserComponent? = null
private set
fun isUserLoggedIn() = userComponent != null
fun logout() {
userComponent = null
}
private fun userJustLoggedIn() {
userComponent = userComponentFactory.create()
}
}
@LoggedUserScope
class UserDataRepository @Inject constructor(private val userManager: UserManager) {
...
}
接下来就是使用UserComponent
class SettingsActivity : AppCompatActivity() {
// @Inject annotated fields will be provided by Dagger
@Inject
lateinit var settingsViewModel: SettingsViewModel
override fun onCreate(savedInstanceState: Bundle?) {
//通过UserManager间接获取UserComponent对象
val userManager = (application as MyApplication).appComponent.userManager()
userManager.userComponent!!.inject(this)
super.onCreate(savedInstanceState)
...
}
...
}
目前demo的依赖图如 下图所示:
2.12 @Provides注解
@Provides 不同于@Binds注解,它可以在Module直接该出一个所需要类型的实例
@Module
class StorageModule {
@Provides
fun provideStorage(context: Context): Storage {
return SharedPreferencesStorage(context)
}
}
@Provides可以告诉Dagger其所需要的抽象的类型的具体类型。但是如果 在一个Module中,需要提供多个不同子类型(或者功能)的Storage
class SharedPreferencesStorage @Inject constructor(name: String, context: Context) : Storage {
private val sharedPreferences = context.getSharedPreferences(name, Context.MODE_PRIVATE)
...
}
则我们需要在Model中需要使用Qualifier类进行限制Provides
@Retention(AnnotationRetention.BINARY)
@Qualifier
annotation class RegistrationStorage
@Retention(AnnotationRetention.BINARY)
@Qualifier
annotation class LoginStorage
@Module
class StorageModule {
@RegistrationStorage
@Provides
fun provideRegistrationStorage(context: Context): Storage {
return SharedPreferencesStorage("registration", context)
}
@LoginStorage
@Provides
fun provideLoginStorage(context: Context): Storage {
return SharedPreferencesStorage("login", context)
}
}
那请求注入方该如何得到正确的注入对象呢?答案还是使用Qualifier
//在方法中使用
class ClassDependingOnStorage(@RegistrationStorage private val storage: Storage) { ... }
//在成员属性中使用
class ClassDependingOnStorage {
@Inject
@field:RegistrationStorage lateinit var storage: Storage
}
三 总结
如果理解了Dagger的Application Graph,对于快速开发大型项目,还是很有帮助的。其能够精确的管理依赖管理和生命周期对象。