Dagger 基础知识

在 Android 应用中使用手动依赖项注入或服务定位器可能会出现问题,具体取决于项目的规模。您可以通过使用 Dagger 来管理依赖项,从而限制项目随着规模扩大而带来的复杂性。

Dagger 自动生成代码,模拟您原本需要手动编写的代码。由于代码是在编译时生成的,因此它可以追溯,并且比其他基于反射的解决方案(如 Guice)性能更高。

使用 Dagger 的优势

Dagger 通过以下方式让您免于编写乏味且容易出错的样板代码

  • 生成您在手动 DI 部分手动实现的 AppContainer 代码(应用程序图)。

  • 为应用程序图中可用的类创建工厂。这是依赖项在内部满足的方式。

  • 通过使用作用域来决定是重用依赖项还是创建新实例。

  • 使用 Dagger 子组件创建特定流程的容器,就像您在上一节中使用登录流程所做的那样。这通过在不再需要时释放内存中的对象来提高应用的性能。

Dagger 在构建时自动执行所有这些操作,只要您声明类的依赖项并使用注释指定如何满足这些依赖项即可。Dagger 生成的代码类似于您手动编写的代码。在内部,Dagger 创建一个对象图,它可以引用该图以找到提供类实例的方法。对于图中的每个类,Dagger 生成一个 工厂类型 类,它在内部使用该类来获取该类型的实例。

在构建时,Dagger 会遍历您的代码并

  • 构建和验证依赖项图,确保

    • 可以满足每个对象的依赖项,因此不会出现运行时异常。
    • 不存在依赖循环,因此不存在无限循环。
  • 生成在运行时用于创建实际对象及其依赖项的类。

Dagger 中的一个简单用例:生成一个工厂

为了演示如何使用 Dagger,让我们为以下图中所示的 UserRepository 类创建一个简单的 工厂

定义 UserRepository 如下

Kotlin

class UserRepository(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }

Java

public class UserRepository {

    private final UserLocalDataSource userLocalDataSource;
    private final UserRemoteDataSource userRemoteDataSource;

    public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
        this.userLocalDataSource = userLocalDataSource;
        this.userRemoteDataSource = userRemoteDataSource;
    }

    ...
}

UserRepository 构造函数中添加一个 @Inject 注解,以便 Dagger 知道如何创建 UserRepository

Kotlin

// @Inject lets Dagger know how to create instances of this object
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }

Java

public class UserRepository {

    private final UserLocalDataSource userLocalDataSource;
    private final UserRemoteDataSource userRemoteDataSource;

    // @Inject lets Dagger know how to create instances of this object
    @Inject
    public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
        this.userLocalDataSource = userLocalDataSource;
        this.userRemoteDataSource = userRemoteDataSource;
    }
}

在上面的代码片段中,您告诉 Dagger

  1. 如何使用带 @Inject 注解的构造函数创建 UserRepository 实例。

  2. 它的依赖项是什么:UserLocalDataSourceUserRemoteDataSource

现在 Dagger 知道如何创建 UserRepository 的实例,但它不知道如何创建其依赖项。如果您也为其他类添加注解,Dagger 就知道如何创建它们。

Kotlin

// @Inject lets Dagger know how to create instances of these objects
class UserLocalDataSource @Inject constructor() { ... }
class UserRemoteDataSource @Inject constructor() { ... }

Java

public class UserLocalDataSource {
    @Inject
    public UserLocalDataSource() { }
}

public class UserRemoteDataSource {
    @Inject
    public UserRemoteDataSource() { }
}

Dagger 组件

Dagger 可以创建项目中依赖项的图形,它可以利用该图形找出在需要时应该从哪里获取这些依赖项。为了让 Dagger 执行此操作,您需要创建一个接口并使用 @Component 为其添加注解。Dagger 创建一个容器,就像您使用手动依赖项注入所做的那样。

@Component 接口内部,您可以定义返回所需类实例(即 UserRepository)的函数。 @Component 告诉 Dagger 生成一个容器,其中包含满足它公开的类型所需的所有依赖项。这称为 *Dagger 组件*;它包含一个图形,该图形由 Dagger 知道如何提供的对象及其各自的依赖项组成。

Kotlin

// @Component makes Dagger create a graph of dependencies
@Component
interface ApplicationGraph {
    // The return type  of functions inside the component interface is
    // what can be provided from the container
    fun repository(): UserRepository
}

Java

// @Component makes Dagger create a graph of dependencies
@Component
public interface ApplicationGraph {
    // The return type  of functions inside the component interface is
    // what can be consumed from the graph
    UserRepository userRepository();
}

构建项目时,Dagger 会为您生成 ApplicationGraph 接口的实现: DaggerApplicationGraph。借助其注解处理器,Dagger 创建一个依赖项图形,该图形由三个类之间的关系组成(UserRepositoryUserLocalDatasourceUserRemoteDataSource),只有一个入口点:获取 UserRepository 实例。您可以按如下方式使用它

Kotlin

// Create an instance of the application graph
val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create()
// Grab an instance of UserRepository from the application graph
val userRepository: UserRepository = applicationGraph.repository()

Java

// Create an instance of the application graph
ApplicationGraph applicationGraph = DaggerApplicationGraph.create();

// Grab an instance of UserRepository from the application graph
UserRepository userRepository = applicationGraph.userRepository();

每次请求时,Dagger 都会创建 UserRepository 的新实例。

Kotlin

val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create()

val userRepository: UserRepository = applicationGraph.repository()
val userRepository2: UserRepository = applicationGraph.repository()

assert(userRepository != userRepository2)

Java

ApplicationGraph applicationGraph = DaggerApplicationGraph.create();

UserRepository userRepository = applicationGraph.userRepository();
UserRepository userRepository2 = applicationGraph.userRepository();

assert(userRepository != userRepository2)

有时,您需要在容器中拥有依赖项的唯一实例。您可能出于以下几个原因需要这样做

  1. 您希望其他将此类型作为依赖项的类型共享同一个实例,例如使用相同 LoginUserData 的登录流程中的多个 ViewModel 对象。

  2. 创建对象很昂贵,并且您不想在每次声明为依赖项时都创建新实例(例如,JSON 解析器)。

在示例中,您可能希望在图形中拥有 UserRepository 的唯一实例,以便每次请求 UserRepository 时,始终获得同一个实例。这在您的示例中很有用,因为在具有更复杂应用程序图形的现实应用程序中,您可能有多个依赖于 UserRepositoryViewModel 对象,并且您不希望每次需要提供 UserRepository 时都创建 UserLocalDataSourceUserRemoteDataSource 的新实例。

在手动依赖项注入中,您可以通过将同一个 UserRepository 实例传递到 ViewModel 类的构造函数中来实现此目的;但在 Dagger 中,因为您不是手动编写该代码,所以您必须让 Dagger 知道您想要使用同一个实例。这可以通过 *作用域注解* 来完成。

使用 Dagger 进行作用域划分

您可以使用作用域注解将对象的生存期限制在其组件的生存期内。这意味着每次需要提供该类型的依赖项时,都使用同一个实例。

为了在您在 ApplicationGraph 中请求存储库时拥有 UserRepository 的唯一实例,请对 @Component 接口和 UserRepository 使用相同的作用域注解。您可以使用 @Singleton 注解,该注解已包含在 Dagger 使用的 javax.inject 包中

Kotlin

// Scope annotations on a @Component interface informs Dagger that classes annotated
// with this annotation (i.e. @Singleton) are bound to the life of the graph and so
// the same instance of that type is provided every time the type is requested.
@Singleton
@Component
interface ApplicationGraph {
    fun repository(): UserRepository
}

// Scope this class to a component using @Singleton scope (i.e. ApplicationGraph)
@Singleton
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val remoteDataSource: UserRemoteDataSource
) { ... }

Java

// Scope annotations on a @Component interface informs Dagger that classes annotated
// with this annotation (i.e. @Singleton) are scoped to the graph and the same
// instance of that type is provided every time the type is requested.
@Singleton
@Component
public interface ApplicationGraph {
    UserRepository userRepository();
}

// Scope this class to a component using @Singleton scope (i.e. ApplicationGraph)
@Singleton
public class UserRepository {

    private final UserLocalDataSource userLocalDataSource;
    private final UserRemoteDataSource userRemoteDataSource;

    @Inject
    public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
        this.userLocalDataSource = userLocalDataSource;
        this.userRemoteDataSource = userRemoteDataSource;
    }
}

或者,您可以创建并使用自定义作用域注解。您可以按如下方式创建作用域注解

Kotlin

// Creates MyCustomScope
@Scope
@MustBeDocumented
@Retention(value = AnnotationRetention.RUNTIME)
annotation class MyCustomScope

Java

// Creates MyCustomScope
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface MyCustomScope {}

然后,您可以像以前一样使用它

Kotlin

@MyCustomScope
@Component
interface ApplicationGraph {
    fun repository(): UserRepository
}

@MyCustomScope
class UserRepository @Inject constructor(
    private val localDataSource: UserLocalDataSource,
    private val service: UserService
) { ... }

Java

@MyCustomScope
@Component
public interface ApplicationGraph {
    UserRepository userRepository();
}

@MyCustomScope
public class UserRepository {

    private final UserLocalDataSource userLocalDataSource;
    private final UserRemoteDataSource userRemoteDataSource;

    @Inject
    public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
        this.userLocalDataSource = userLocalDataSource;
        this.userRemoteDataSource = userRemoteDataSource;
    }
}

在这两种情况下,都使用用于为 @Component 接口添加注解的相同作用域来提供对象。因此,每次调用 applicationGraph.repository() 时,您都会获得同一个 UserRepository 实例。

Kotlin

val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create()

val userRepository: UserRepository = applicationGraph.repository()
val userRepository2: UserRepository = applicationGraph.repository()

assert(userRepository == userRepository2)

Java

ApplicationGraph applicationGraph = DaggerApplicationGraph.create();

UserRepository userRepository = applicationGraph.userRepository();
UserRepository userRepository2 = applicationGraph.userRepository();

assert(userRepository == userRepository2)

结论

在您可以将 Dagger 用于更复杂的场景之前,了解 Dagger 的优势以及它如何工作的基础知识非常重要。

下一页,您将学习如何将 Dagger 添加到 Android 应用程序中。