根据项目的大小,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:
-
如何使用带
@Inject注释的构造函数创建UserRepository实例。 -
它的依赖项是:
UserLocalDataSource和UserRemoteDataSource。
现在,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)
有时,您需要在容器中具有依赖项的唯一实例。您可能出于以下几个原因而希望这样做:
-
您希望具有此类型作为依存关系的其他类型共享同一实例,例如
ViewModel使用相同的登录流中的多个对象LoginUserData。 -
创建一个对象很昂贵,并且您不想每次将其声明为依赖项时都创建一个新实例(例如,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应用程序。