[译]Dagger基础

493 阅读6分钟

根据项目的大小,Android应用程序中的手动依赖注入或service locators可能会出现问题。您可以使用Dagger来管理依赖项,从而限制项目的复杂性。

Dagger会自动生成模仿您本来要手写的代码的代码。由于代码是在编译时生成的,因此与其他基于反射的解决方案(例如Guice)相比,它具有可追溯性和更高的性能 。

使用Dagger的好处

Dagger通过以下方式使您不必编写繁琐且容易出错的样板代码:

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

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

  • 重用依赖关系或创建类型的新实例,具体取决于您如何使用范围对类型进行配置。

  • 像上一节中使用Dagger子组件处理登录流程一样,为特定流程创建容器。当不需要的对象释放到内存中时,这可以提高应用程序的性能。

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

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

  • 构建并验证依赖关系图,确保:

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

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

定义UserRepository如下:

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

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知道如何创建它们:

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

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

Dagger组件

Dagger可以在您的项目中创建依赖关系图,可用于查找需要时从何处获取这些依赖关系。为了使Dagger做到这一点,您需要创建一个接口并使用@Component对其进行注释。Dagger会创建一个容器,就像使用手动依赖项注入一样。

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

// @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创建一个包含三个类(UserRepository,UserLocalDatasource以及UserRemoteDataSource)之间的关系和一个只有一个得到一个UserRepository实例入口点的依赖关系图。您可以按以下方式使用它:

// 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实例。

ApplicationGraph applicationGraph = DaggerApplicationGraph.create();

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

assert(userRepository != userRepository2)

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

  1. 您希望具有此类型作为依存关系的其他类型共享同一实例,例如ViewModel使用相同的登录流中的多个对象 LoginUserData

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

在该示例中,您可能希望在图中拥有一个UserRepository可用的唯一实例,以便每次您请求时UserRepository,您总是会得到相同的实例。这在您的示例中很有用,因为在具有更复杂应用程序图的现实应用程序中,您可能有多个ViewModel对象,UserRepository并且您不希望为其创建新实例, UserLocalDataSource并且UserRemoteDataSource 每次都UserRepository需要提供它。

在手动依赖项注入中,您可以通过将相同的实例传递UserRepository给ViewModel类的构造函数来实现;但是在Dagger中,由于您不是手动编写该代码,因此必须让Dagger知道您要使用同一实例。这可以通过范围注释来完成。

Dagger作用域

您可以使用范围注释将对象的生存期限制为其组件的生存期。这意味着每次需要提供该类型时,都将使用相同的依赖项实例。

要UserRepository在请求存储库时有一个独特的实例,请对 接口和ApplicationGraph使用相同的作用域注释。您可以使用Dagger使用的软件包随附的注释:@ComponentUserRepository@Singletonjavax.inject

// 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;
    }
}

或者,您可以创建和使用自定义范围注释。您可以如下创建作用域注释:

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

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

@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。

ApplicationGraph applicationGraph = DaggerApplicationGraph.create();

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

assert(userRepository == userRepository2)

结论

在更复杂的场景中使用Dagger之前,请务必了解Dagger的优点及其工作原理,这一点很重要。

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