Android开源框架之Dagger快速入门


一、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"
}

最新Dagger传送门

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,对于快速开发大型项目,还是很有帮助的。其能够精确的管理依赖管理和生命周期对象。

参考链接:
示例代码
官方链接

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值