笔记|Dagger2入门笔记

概述


又是很久没有更新掘金了~其实个人我的博客上一直有记笔记记笔记,不过没有整理到这边来。最近刚换了份工做,以前也分享过面经儿——两年Android开发大厂面试经验,忙于熟悉新业务,本身要学习增强的东西还不少,好好学习,每天向上~java


Dagger 的名字取自有向无环图 DAG (directed acyclic graph),由于程序里的依赖关系拼接起来就是一个或者多个有向无环图。android

首先理解一下什么是依赖注入。一个类 UserRepository 中有一个 UserRemoteDataSource 类型的属性, 那 UserRemoteDataSource 即是 UserRepository 的依赖,初始化这个依赖能够有两种方法,一种是在类内部本身初始化,另外一种是由外部初始化传入(便是依赖注入,关键在于初始化是谁作的)。git

这种由外部初始化的方式均可以叫作依赖注入,而 Dagger 则为依赖注入提供了一种更简单的方式。github

基础用法

接下来用 Dagger2 实现以下图所示的依赖关系:web

首先添加 Dagger2 的依赖:面试

apply plugin: 'kotlin-kapt'

dependencies {
    implementation 'com.google.dagger:dagger:2.x'
    kapt 'com.google.dagger:dagger-compiler:2.x'
}
复制代码

而后定义 UserRepository 类:markdown

class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) {
    fun load(): String = "${localDataSource.load()} - ${remoteDataSource.load()}"
}
复制代码

这里经过在构造器上添加 @Inject 注解告知 Dagger 如何建立 UserRepository 实例,它的依赖项为 UserLocalDataSource 和 UserRemoteDataSource, 为了让 Dagger 知道如何建立这两个依赖项实例,也需使用 @Inject 注解:app

interface DataSource {
    fun load(): String
}

class UserRemoteDataSource @Inject constructor() : DataSource {
    override fun load(): String = "UserRemoteDataSource.load()"
}

class UserLocalDataSource @Inject constructor() : DataSource {
    override fun load(): String = "UserLocalDataSource.load()"
}
复制代码

这样 Dagger 便会知道如何建立它们。ide

而后在 ViewModel 中也能够经过这种方式依赖 UserRepository:函数

class LoginViewModel @Inject constructor(private val userRepository: UserRepository) {
    fun loadData(): String = userRepository.load()
}
复制代码

接下来在 Activity 中怎么引用 LoginViewModel 实例呢?显然它不能跟上面同样在构造方法中使用 @Inject 注解了,由于 Activity 实例化是由系统完成的,没法由 Dagger 去进行实例化,所以这里不能由构造函数注入 LoginViewModel, 而应该使用属性注入(注意:注入的属性不能为私有属性):

class LoginActivity : AppCompatActivity() {
    @Inject
    lateinit var viewModel: LoginViewModel
}
复制代码

光是这样显然是不够的,咱们还须要告知 Dagger 要求注入依赖项的对象是谁(LoginActivity),在此以前就得先看看 Dagger Component 的概念。

Component

Dagger 能够在项目中建立一个有向无环图(依赖关系),而后它能够从该图中了解在须要这些依赖项时从何处获取它们。为了让 Dagger 执行此操做,咱们须要建立一个接口,并使用 @Component 为其添加注解,在上面的基础用法里,Dagger 须要知道 LoginActivity 中的 LoginViewModel 依赖应该怎么建立:

属性注入

对于属性注入,Dagger 须要让 LoginActivity 访问该图才能提供所需的 LoginViewModel 依赖实例,为此应在 Component 接口中提供一个函数,让该函数将请求注入的对象做为参数。

@Component
interface ApplicationComponent {
    fun inject(activity: LoginActivity)
}
复制代码

@Component 会让 Dagger 生成一个容器,其中应包含知足其提供的类型所需的全部依赖项,这称为 Dagger 组件,它包含一个图,其中包括 Dagger 知道如何提供的对象及其各自的依赖项。在 build 项目时 Dagger 会生成 ApplicationComponent 接口的实现:DaggerApplicationComponent。Dagger 经过其注解处理器(处理 @Inject 等)建立了一个依赖关系图,包含了这些依赖之间的关系,而且将 ApplicationComponent 接口中咱们加入的方法(inject 方法,方法名能够随意)涉及的依赖做为入口点。

在声明了 inject 方法后,咱们能够这样实现注入:

class LoginActivity : AppCompatActivity() {
    @Inject
    lateinit var viewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        DaggerApplicationComponent.create().inject(this)
        super.onCreate(savedInstanceState)
        println(viewModel.loadData())
    }
}
复制代码

构造器注入

在上面的 LoginActivity 中,若是不经过属性注入 LoginViewModel 依赖的话,还能够经过构造器注入的方式,这里须要在 @Component 接口内定义返回所需类(即 LoginViewModel)的实例的函数,这样 Dagger 就知道怎么建立它了。

@Component
interface ApplicationComponent {
    fun viewModel(): LoginViewModel
}
复制代码

而后在 LoginActivity 中给 viewModel 赋值:

class LoginActivity : AppCompatActivity() {
    private val viewModel: LoginViewModel = DaggerApplicationComponent.create().viewModel()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        println(viewModel.loadData())
    }
}
复制代码

Module

除了 @Inject 以外,还有一种方法可告知 Dagger 如何提供类实例 —— @Module@Provides

假设将上例中的 UserRemoteDataSource 构造方法改一下,其依赖于 UserService 类:

class UserRemoteDataSource @Inject constructor(private val userService: UserService) : DataSource {
    override fun load(): String = userService.load()
}

class UserService private constructor() {
    class Builder {
        fun build(): UserService = UserService()
    }

    fun load(): String = "UserService.load()"
}
复制代码

能够看到 UserService 对外的建立方法不是经过构造函数,而是经过其 Builder 类来实例化的,那这里就不能使用 @Inject 来进行注入依赖了,在实际项目中这样的场景也很多,咱们可使用 @Module 注解来定义 Dagger 模块,使用 @Provides 注解定义依赖项:

@Module
class NetworkModule {
    @Provides
    fun provideUserService(): UserService {
        return UserService.Builder().build()
    }
}

@Component(modules = [NetworkModule::class])
interface ApplicationComponent { 
    fun viewModel(): LoginViewModel
}
复制代码

上面的 Module 注解用来告知 Dagger 这是一个 Module 类,而 Provides 注解用来告诉 Dagger 怎么建立该方法返回的类型的实例。这里若是要建立的 UserService 实例须要依赖其余对象,能够将依赖做为方法参数传入,固然该依赖也须要使用 Inject 等注解来告知 Dagger 怎么去建立它。

做用域限定

在上面经过 DaggerApplicationComponent.create().viewModel() 建立的 ViewModel 实例,同一个 DaggerApplicationComponent 对象调用屡次 viewModel() 方法,Dagger 默认会建立多个不一样的 ViewModel 实例。能够验证:

val component = DaggerApplicationComponent.create()
println(component.viewModel() == component.viewModel()) // false
复制代码

若是须要让 Dagger 中同一个 DaggerApplicationComponent 对象始终返回同一 viewModel 实例,可使用做用域注解(@Singleton)将某个对象的生命周期限定为其 Component 对象的生命周期,这意味着每次获取时都会使用该依赖项的同一实例。固然,若是是不一样的 DaggerApplicationComponent 对象,那么 viewModel 天然也不同。

@Singleton
@Component
interface ApplicationComponent {
    fun viewModel(): LoginViewModel
}

@Singleton
class LoginViewModel @Inject constructor(private val userRepository: UserRepository) {
    fun loadData(): String = userRepository.load()
}

val component = DaggerApplicationComponent.create()
println(component.viewModel() == component.viewModel()) // true
复制代码

@Singletonjavax.inject 软件包随附的惟一一个做用域注解,也能够建立并使用自定义做用域注解,使用方式跟 @Singleton 同样:

@Scope
@MustBeDocumented
@Retention(value = AnnotationRetention.RUNTIME)
annotation class MyCustomScope
复制代码

也能够对 Module 中提供的依赖使用与 Component 同样的做用域注解,这样 Provides 方法中返回的依赖也就与 Component 对象有了同样的生命周期了:

@Module
class NetworkModule {
    @Singleton
    @Provides
    fun provideUserService(): UserService {
        return UserService.Builder().build()
    }
}
复制代码

注意:使用做用域注解的依赖只能在带有相同做用域注解的 Component 中使用

在 Android 中咱们一般会建立一个位于 Application 类中的 Dagger 图,这样就将该图附加到应用的生命周期里了:

class MainApplication : Application() {
    val applicationGraph = DaggerApplicationComponent.create()
}
复制代码

因而上面与该 ApplicationComponent 使用同一 @Singleton 注解的依赖项也将得到与其一致的生命周期,经过该 ApplicationComponent 对象中的方法获取到的依赖对象(applicationGraph.viewModel()等),其生命周期将与其一致

做用域限定规则

  • 若是某个依赖类标记有做用域注解,该类型就只能由带有相同做用域注解的组件 Component 使用。
  • 若是某个组件 Component 标记有做用域注解,该组件就只能提供带有该注解的类型或不带注解的类型。
  • 子组件(后面会讲到)不能使用其某一父组件使用的做用域注解。

Subcomponent

为何使用子组件

上面咱们在 MainApplication 应用类中建立了一个 DaggerApplicationComponent 实例,当经过这个 applicationGraph 实例去获取依赖的时候,对于使用 Singleton 注解的依赖(注意 Component 也须要用 Singleton 注解),则其会跟应用生命周期一致(蕾丝于全局单例)。

如需将 LoginViewModel 的做用域限定为 LoginActivity 的生命周期,咱们能够在 LoginActivity 中建立 DaggerApplicationComponent 实例属性,而后经过这个实例去获取 LoginViewModel 对象,因为此时这个 DaggerApplicationComponent 实例的生命周期跟 LoginActivity 绑定,则 LoginViewModel 的做用域也会被限定为 LoginActivity 的生命周期:

class LoginActivity : AppCompatActivity() {
    @Inject
    lateinit var viewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        DaggerApplicationComponent.create().inject(this)
        super.onCreate(savedInstanceState)
        println("Current: $viewModel, ${viewModel.loadData()}")
    }
}
复制代码

经过上面这个方式,能够将 LoginViewModel 的做用域限定为 LoginActivity 的生命周期,但因为 DaggerApplicationComponent 被定义为应用全局的依赖图,而这里咱们所需的依赖仅是 LoginActivity 相关的,且又要限制 LoginViewModel 的做用域,为此又在 LoginActivity 中反复建立 DaggerApplicationComponent 实例,显得不那么好看。并且 LoginViewModel 的依赖应该只有登录这些场景才会使用,能够避免将其放到应用全局的依赖图里。所以这里能够考虑使用子组件(子依赖图),应用组件 DaggerApplicationComponent 中只放一些共有的依赖图,不一样的应用场景考虑增长子图,子图存放特有应用/业务场景的依赖,如 LoginViewModle 就放在登录子图 LoginComponent 中

子组件是继承并扩展父组件的对象图的组件,所以,父组件中提供的全部对象也将在子组件中提供,这样子组件中的对象就能够依赖于父组件提供的对象(共有),父组件向子组件提供的对象的做用域仍限定为父组件的生命周期。

建立子组件

一、定义子组件

@Subcomponent
interface LoginComponent {
    // 定义子组件 Factory 使得 ApplicationComponent 知道如何建立 LoginComponent 的实例
    @Subcomponent.Factory
    interface Factory {
        fun create(): LoginComponent
    }

    fun inject(loginActivity: LoginActivity)
}
复制代码

二、建立子组件的模块

@Module(subcomponents = [LoginComponent::class])
class SubcomponentsModule {
}
复制代码

三、将声明子组件的模块添加到 ApplicationComponent 中

@Singleton
@Component(modules = [NetworkModule::class, SubcomponentsModule::class])
interface ApplicationComponent {
    // 告知 Dagger 如何建立 LoginComponent.Factory
    // 而 LoginComponent.Factory 会用来建立子组件 LoginComponent 实例
    fun loginComponent(): LoginComponent.Factory

    // 将 UserRepository 依赖放入 ApplicationComponent 图中
    fun repository(): UserRepository
}
复制代码

注意,这里 ApplicationComponent 以前的 inject 方法删除了,由于 inject 放到了专门的登录子组件中。增长了一个返回 UserRepository 实例的 repository() 方法,若是不加这个方法且 UserRepository 没有跟 ApplicationComponent 同样用 Singleton 注解标识的话,UserRepository 就会被包含到 LoginComponent 依赖图中,由于它被 LoginViewModel 引用了,这是目前使用它的惟一位置,而 LoginViewModel 是 LoginComponent 子组件中的依赖。如今的依赖关系以下图:

四、使用

class LoginActivity : AppCompatActivity() {
    @Inject
    lateinit var viewModel: LoginViewModel

    lateinit var loginComponent: LoginComponent

    override fun onCreate(savedInstanceState: Bundle?) {
        loginComponent = (application as MainApplication).applicationGraph.loginComponent().create()
        loginComponent.inject(this)
        super.onCreate(savedInstanceState)
        println("Current: $viewModel, ${viewModel.loadData()}")
    }
}
复制代码

LoginComponent 是在 Activity 的 onCreate() 方法中建立的,将随着 Activity 的销毁而被隐式销毁。这样它就具有了跟 LoginActivity 一致的生命周期了。

做用域限定

能够建立自定义做用域注解,并使用该做用域为 LoginComponent 和 LoginViewModel 添加注解。此时若是存在两个须要 LoginViewModel 依赖的 Fragment 页面,则咱们在这两个 Fragment 中注入的 LoginViewModel 都是同一个实例(注意不能使用 Singleton 注解,由于其已经被父组件占用了)。

能够将该注解命名为 ActivityScope, 这里不使用 LoginScope, 由于该注解也能够被其余蕾丝的场景(同级组件)使用。

@Scope
@Retention(value = AnnotationRetention.RUNTIME)
annotation class ActivityScope

@ActivityScope
@Subcomponent
interface LoginComponent {
    // 定义子组件 Factory 使得 ApplicationComponent 知道如何建立 LoginComponent 的实例
    @Subcomponent.Factory
    interface Factory {
        fun create(): LoginComponent
    }

    fun inject(loginActivity: LoginActivity)
    fun inject(fragment: LoginAFragment)
    fun inject(fragment: LoginBFragment)
}

@ActivityScope
class LoginViewModel @Inject constructor(private val userRepository: UserRepository) { ... }
复制代码

而后在这两个 Fragment 中能够注入 LoginViewModel 实例:

class LoginAFragment: Fragment() {
    @Inject
    lateinit var loginViewModel: LoginViewModel

    override fun onAttach(context: Context) {
        super.onAttach(context)
        (activity as LoginActivity).loginComponent.inject(this)
    }
}
复制代码

组件依赖

一个 Component 能够经过设置 dependencies 依赖另外一个 Component 组件,而后它即可获取到所依赖的 Component 中暴露的依赖

咱们首先建立一个零件组件:

@Singleton
class Memory @Inject constructor() {
    override fun toString(): String = "Memory"
}

@Singleton
class Cpu @Inject constructor() {
    override fun toString(): String = "Cup"
}

@Singleton
@Component
interface PartComponent {
    fun cup(): Cpu
    fun memory(): Memory
}
复制代码

这里使用 Singleton 注解将 Memory 和 Cpu 限制到 PartComponent 零件组件的做用域里。

接着假设一个 Computer 类须要依赖 Cpu 和 Memory:

@ActivityScope // 看实际场景,可加可不加
class Computer @Inject constructor(
    private val cpu: Cpu,
    private val memory: Memory
) {
    override fun toString(): String = "$cpu & $memory"
}
复制代码

而后定义一个依赖 PartComponent 组件的 ComputerComponent 组件:

// 须要加做用域,在新版本 Dagger 中没加做用域的话,依赖的 PartComponent 组件若是有做用域注解则会编译报错
// ComputerComponent (unscoped) cannot depend on scoped components
@ActivityScope
@Component(dependencies = [PartComponent::class])
interface ComputerComponent {
    fun inject(activity: MainActivity)
}
复制代码

在这里 ComputerComponent 依赖 PartComponent 组件,所以它可获取到 PartComponent 接口中暴露的依赖:

class MainActivity : AppCompatActivity() {
    @Inject
    lateinit var computer: Computer

    override fun onCreate(savedInstanceState: Bundle?) {
        val partComponent = DaggerPartComponent.create()
        val computerComponent = DaggerComputerComponent.builder().partComponent(partComponent).build()
        computerComponent.inject(this)
        super.onCreate(savedInstanceState)
        println(computer) // Cup & Memory
    }
}
复制代码

这里咱们若是将 PartComponent 接口中暴露依赖的两个方法删掉,那么 ComputerComponent 便不能访问到 Cpu 和 Memory 了,编译报错,由于它会直接尝试去找 Computer 的两个依赖,而后发现这两个依赖被 Singleton 注解标识了,而 ComputerComponent 自身是 ActivityScope 做用域的,所以编译报错(以前咱们说依赖若是有做用域注解的话,其做用域注解要和组件的一致)。

关于组件依赖还可举例,依旧用上面的 Cpu 和 Memory 类:

class Cpu {
    override fun toString(): String = "Cup"
}

class Memory {
    override fun toString(): String = "Memory"
}

@Module
class PartModule {
    @Provides
    fun provideCpu(): Cpu = Cpu()

    @Provides
    fun provideMemory(): Memory = Memory()
}
复制代码

这里咱们经过 Module 的方式提供依赖。接着在 PartComponent 组件中声明模块,并暴露出获取依赖的方法。

@Component(modules = [PartModule::class])
interface PartComponent {
    fun cup(): Cpu
    fun memory(): Memory
}
复制代码

而后跟上面同样让 ComputerComponent 去依赖 PartComponent 并注入 Computer,这里能够视状况增长做用域限定。

总结

Google 在 Jetpack 包中增长了 Hilt 组件,它是专门针对 Android 平台作的一个依赖注入库,底层依旧是基于 Dagger, 所以学习 Dagger 仍是挺有必要的。Hilt 并非对 Dagger 作了优化,而是针对 Android 开发制定了一套场景化的规则,刚学习了 Dagger 的一些用法,后续有时间接着学习一下 Hilt 及其实现原理。

Dagger 的用法会比较复杂一点,这篇文章结合了我本身的一些理解和用例,讲解了一下 Dagger 的基础用法,但愿对有须要的同窗能有所帮助。

文中内容若有错误欢迎指出,共同进步!以为不错的留个再走哈~

博文连接

参考:

相关文章
相关标签/搜索