Dependency Injection with Dagger 2
原文:Dependency Injection with Dagger 2
创建单例
下面展示一个简单的Dagger来管理所有单例创建的例子:
1 | OkHttpClient client = new OkHttpClient(); |
声明你的单例
我们需要一个和 Application 生命周期绑定的用来提供给 Activity 和 Fragment 的 Retrofit 。
因为我们希望使用缓存,所以需要一个 Application context。首先新建 AppModule.java
模块来提供这个引用。我们将定义一个使用 @Provides
注释的方法,来使 Dagger 知道该方法是 Application 返回类型的构造函数。
1 | @Module |
我们创建了一个名为 NetModule.java
的类,并使用 @Module 对其进行注释,以便向 Dagger 发出信号,以便在可用的方法中搜索可能的实例提供程序。
实际上返回公共可用类型的方法也应该使用 @Provides 注释进行注释。@Singleton 注释还向 Dagger 编译器发出信号,表明该实例应该只在应用程序中创建一次。In the following example, we are specifying SharedPreferences
, Gson
, Cache
, OkHttpClient
, and Retrofit
as the return types that can be used as part of the dependency list.
1 | @Module |
注意方法可以随意命名。使用 @Provides 注释的方法的返回类型将用于此实例化与任何其他相同类型的模块相关联。@Singleton 注释用于声明 Dagger 仅在应用程序的整个生命周期中初始化一次。
定义注解目标
Dagger 为您的 activities, fragments, services 中的 field 提供了一种方法,只需通过使用 @Inject 批注注释字段并调用inject() 方法即可为其分配引用。调用 inject()
将使 Dagger2 在依赖表中找到对应返回类型的单例。如果找到一个,它会将引用分配给相应的字段。例如,在下面的示例中,它将尝试查找返回 MyTwitterApiClient 和 SharedPreferences 类型的提供者。
1 | public class MainActivity extends Activity { |
Dagger 2中使用的注入器类称为 component。它在我们的 activities, fragments, services 中分配引用,以访问我们之前定义的单例。我们需要用 @Component
来注解该类。注意,允许请求模块声明的依赖项的 activities, fragments, services (通过 @Inject 注解)应该在此类中使用单独的i nject() 方法声明:
1 | @Singleton |
请注意,基类不能作为注入目标。Dagger 2 依赖于强类型类,因此必须明确指定应定义哪些类。
生成代码
Dagger 2 的一个重要方面是该库为使用 @Component 接口注释的类生成代码。您可以使用以 Dagger 为前缀的类(即DaggerTwitterApiComponent.java),该类将负责实例化我们的依赖关系图的实例,并使用它来为使用@Inject注释的字段执行注入工作。
实例化 Component
我们应该在 Application 类的初试化中完成所有这些工作,因为这些实例应该在应用程序的整个生命周期中只声明一次:
1 | public class MyApp extends Application { |
在我们的 Activity 中,我们只需要访问这些 Component 并调用 inject()。
1 | public class MyActivity extends Activity { |
Qualified types(限定值类型)
如果我们需要一种返回类型的不同对象,我们可以使用 @Named
限定符注解。需要在提供单例(@Provides注释)的位置以及注入它们的位置(@Inject注释)中定义它:
1 | @Provides |
1 | @Inject |
@Named
是 Dagger 预定义的限定符,但是你也可以创建你自己的限定符注解:
1 | @Qualifier |
Scopes(作用域)
在Dagger 2中,您可以通过定义自定义 scopes 来定义组件应如何封装。例如,您可以创建仅存在于 Activity、Fragment 生命周期持续时间的范围。您可以创建仅映射到用户身份验证会话的范围。您可以通过将它们声明为公共 @interface 来定义程序中的任意数量的自定义范围注释。
1 | @Scope |
即使 Dagger 2 在运行时不依赖于注解,将 RetentionPolicy 保留在RUNTIME在让您以后检查 Module 时也很有用。
Dependent Components vs. Subcomponents
利用 scopes 允许我们创建 dependent components 或 subcomponents。上面的例子表明我们使用了持续应用程序整个生命周期的 @Singleton 注释。我们还依赖于一个主要的 Dagger 组件。
如果我们不希望有多个组件一直保留在内存中(即与 Activity 或 Fragment 的生命周期相关联的 component ,甚至与用户登录时绑定的 component ),我们可以创建 dependent components 或 subcomponents。在任何一种情况下,每种方法都提供了一种封装代码的方法。我们将在下一节中看到如何使用它们。
使用这些方法时有几个注意事项:
Dependent components 要求父组件明确列出可以向下游注入的依赖项,而 subcomponents 则不需要。对于父 components,您需要通过指定类型和方法来向下游组件公开:
1
2
3
4
5
6
7
8
9
10
11
12// parent component
@Singleton
@Component(modules={AppModule.class, NetModule.class})
public interface AppComponent {
// remove injection methods if downstream modules will perform injection
// downstream components need these exposed
// the method name does not matter, only the return type
Retrofit retrofit();
OkHttpClient okHttpClient();
SharedPreferences sharedPreferences();
}如果您忘记添加此行,您可能会看到有关缺少注射目标的错误。与私有/公共变量的管理方式类似,使用父 components 允许更明确的控制和更好的封装,但使用子组件使得依赖注入更容易管理,代价是更少的封装。
两个 dependent components 不能共享同一个 scope。例如,两个组件不能同时作为 @Singleton 作用域(原因)。dependent components 需要定义自己的 scope。
- 虽然 Dagger 2 还支持创建范围实例的功能,但您有责任创建和删除与预期行为一致的引用。Dagger 2对底层实现一无所知。(更多细节)。
Dependent Components
例如,如果我们希望使用为签署到应用程序的用户会话的整个生命周期创建的组件,我们可以定义自己的 UserScope 接口:
1 | @Scope |
接下来我们定义父 component:
1 | @Singleton |
然后我们定义子component:
1 | @UserScope // using the previously defined scope, note that @Singleton will not work |
我们假设这个 GitHub 模块只返回一个 API 接口到 GitHub API:
1 | @Module |
为了使 GitHubModule.java
能够访问 Retrofit 实例,我们需要在上游组件中明确定义它们。 如果下游模块将执行注入,它们也应该从上游组件中移除:
1 | @Singleton |
最后一步是使用 UserComponent
来执行实例化。这次,我们首先需要构建 AppComponent
并将其传递给 DaggerUserComponent
构建器的构造函数:
1 | AppComponent mAppComponent = DaggerAppComponent.builder() |
Subcomponents
使用 subcomponents 是扩展 component 对象图的另一种方法。与具有依赖关系的 components 一样,subcomponents 具有自己的生命周期,并且当对 subcomponents 的所有引用都消失并且具有相同的 scope 限制时可以进行垃圾收集。使用此方法的一个优点是您无需定义所有下游 components。
另一个主要区别是 subcomponents 只需要在父组件中声明。
以下是 Activity 使用 subcomponent 的示例。我们使用自定义 scope 和 @Subcomponent 注解来注解该类:
1 | @MyActivityScope |
将使用的 module 定义如下:
1 | @Module |
最后,在父 component 中,我们将定义一个工厂方法,该方法具有 component 的返回值和实例化它所需的依赖项:
1 | @Singleton |
在上面的示例中,每次调用 newMyActivitySubcomponent()
时都会创建子 component 的新实例。要使用子 module 注入Activity:
1 | public class MyActivity extends Activity { |
Subcomponent Builders
Subcomponent builders
通过消除在该父 component 上声明的子 component 工厂方法的需要,子 component 构建器允许子 component 的创建者与父 component 解耦。
1 | @MyActivityScope |
Subcomponent 构建器在 Subcomponent 接口中声明为内部接口,并且必须包含与 Subcomponent 匹配的返回类型的build() 方法。使用此方法声明基接口很方便,如上面的 SubcomponentBuilder 。必须使用带有“subcomponents”参数的“binder”模块将此新构建器添加到父组件图中:
1 | @Module(subcomponents={ MyActivitySubComponent.class }) |
一旦构建器在 component 图中可用,Activity 就可以使用它来创建其子组件:
1 | public class MyActivity extends Activity { |
ProGuard
Dagger 2 should work out of box without ProGuard, but if you start seeing library class dagger.producers.monitoring.internal.Monitors$1 extends or implements program class javax.inject.Provider
, make sure your Gradle configuration uses the annotationProcessor
declaration instead of provided
.
Troubleshooting
- If you are upgrading Dagger 2 versions (i.e. from v2.0 to v2.5), some of the generated code has changed. If you are incorporating Dagger code that was generated with older versions, you may see
MemberInjector
andactual and former argument lists different in length
errors. Make sure to clean the entire project and verify that you have upgraded all versions to use the consistent version of Dagger 2.
References
- Dagger 2 Github Page
- Sample project using Dagger 2
- Vince Mi’s Codepath Meetup Dagger 2 Slides
- http://code.tutsplus.com/tutorials/dependency-injection-with-dagger-2-on-android--cms-23345
- Jake Wharton’s Devoxx Dagger 2 Slides
- Jake Wharton’s Devoxx Dagger 2 Talk
- Dagger 2 Google Developers Talk
- Dagger 1 to Dagger 2
- Tasting Dagger 2 on Android
- Dagger 2 Testing with Mockito
- Snorkeling with Dagger 2
- Dependency Injection in Java
- Component Dependency vs. Submodules in Dagger 2
- Dagger 2 Component Scopes Test
- Advanced Dagger Talk