本文由于潜在的商业目的,不开放全文转载许可,谢谢!
翻译:asce1885
原文链接:http://antonioleiva.com/dagger-android-part-2/
如果你阅读了关于依赖注入的 第一篇文章,你可能期待看到真实的代码。在 Dagger 网页上面可以找到 coffee makers 优美的例子,以及由 Jake Wharton 创建的,更适用于有经验读者的很棒的 示例工程。但我们需要更简单的例子,coffee makers 并不是我们主要的业务模式。因此,本文会提供简单的例子,展示注入简单的组件,让我们理解基本知识。
本文介绍的例子源码可以在这个页面找到: https://github.com/antoniolg/DaggerExample;
在工程中引入 Dagger
如果想使用 Dagger 的话,需要添加两个函数库:
dependencies {
compile 'com.squareup.dagger:dagger:1.2.+'
provided 'com.squareup.dagger:dagger-compiler:1.2.+'
}
第一个是 Dagger 函数库,第二个是 Dagger 编译器函数库,它会创建注入依赖所需的类。通过创建预编译的类可以避免大部分的反射操作。由于我们只需要 Dagger 编译器函数库来编译工程,在应用中不会使用到它,因此我们把它标记为 provided,这样在最终的 apk 中就不会包含这个函数库了。
创建第一个module
Modules 是使用 Dagger 经常碰到的概念,所以你需要习惯它们。Modules 是提供依赖注入时所需对象实例的类,它们通过 @Module 注解来修饰类。还有其他一些额外参数可能需要配置,但我们会在使用到的时候再进行介绍。
创建一个名为 AppModule 的类,假设用于提供 Application Context。为 Application Context 提供简单的访问入口通常是有趣的。我创建继承自 Application 的类 App,并添加到 AndroidManifest 文件中。
@Module(
injects = {
App.class
}
)
public class AppModule {
private App app;
public AppModule(App app) {
this.app = app;
}
@Provides
@Singleton
public Context provideApplicationContext() {
return app;
}
}
上面涉及到哪些新知识呢?
@Module:把这个类标识为 Dagger module
injects:标识 module 将要注入这个类的任何依赖。我们需要明确指定将直接注入到对象图中的那些类,这将很快会讲到。
@Providers:标识函数作为注入提供者,函数名并不重要,它只依赖于所提供的类类型。
@Singleton:如果标识为 Singleton,那这个函数会一直返回相同的对象实例,这比常规的单例好很多。否则,每次注入类型都会得到一个新的实例。在这个例子中,由于我们没有创建新实例,而是返回已经存在的实例,因此即使不把函数标识为 Singleton,每次调用还是会返回相同的实例的,但这样能够更好地说明提供者到底做了什么。Application 实例是唯一的。
为什么常规的单例是不好的
单例可能是一个工程能够具有的最危险的依赖了。首先,由于我们没有创建实例,因此很难知道要在什么地方使用它,因此具有“隐藏依赖”。另外,我们没有办法通过 mock 来测试它,或者以另外的模块替换它,因此代码变得难以维护,测试和进化。相反,被注入的单例具有单例的优势(唯一的实例),同时,由于我们可以在任何时候创建新的实例,因此很容易 mock 或者通过子类化或者使其实现一个公有接口来使用另一个代码片段替换它。
我们将在一个新的名为 domain 的包里面创建另外一个 module。在每个架构层拥有至少一个 module 是很有用的。这个 module 将提供一个统计管理器,在 app 启动时通过显示一个 Toast 来抛出一个事件。在实际的工程中,这个管理器可能调用任何统计服务例如 Google Analytics。
@Module(
complete = false,
library = true
)
public class DomainModule {
@Provides
@Singleton
public AnalyticsManager provideAnalyticsManager(Application app) {
return new AnalyticsManager(app);
}
}
通过把这个 module 指明为未完成,表示这个 module 的某些依赖需要另外一个 module 来提供。也就是 AppModule 里面的 Application。当我们从一个依赖注入请求这个 AnalyticsManager 时,dagger 将会使用这个函数,并会检测到它需要另外一个依赖:Application,这会通过向对象图发起请求来获得。我们同时需要把 module 指明为 library,因为 dagger 编译器会检测到 AnalyticsMananger 没有被它自身或者它的注入类所使用。对于 AppModule 来说,DomainModule 就是一个库模块。
我们将指定 AppModule 会包含 DomainModule,因此返回 AppModule 类,修改代码如下:
@Module(
injects = {
App.class
},
includes = {
DomainModule.class
}
)
public class AppModule {
...
}
includes 属性正是用来实现这个目的的。
创建对象图(ObjectGraph)
对象图是所有依赖存在的地方,它包含被创建的实例,并能够把这些实例注入到相应的对象中。
在前面的例子中(AnalyticsManager)我们看到了经典的依赖注入,注入是通过构造函数参数传递的。但在 Android 中有的类(Application,Activity)我们对构造函数没有控制权,因此我们需要另外一种方式来注入依赖。
对象图创建和直接注入的组合方式在 App 类中可以看到。主对象图在 Application 类中创建并被注入以获得依赖。
public class App extends Application {
private ObjectGraph objectGraph;
@Inject
AnalyticsManager analyticsManager;
@Override
public void onCreate() {
super.onCreate();
objectGraph = ObjectGraph.create(getModules().toArray());
objectGraph.inject(this);
analyticsManager.registerAppEnter();
}
private List<Object> getModules() {
return Arrays.<Object>asList(new AppModule(this));
}
}
我们通过 @Inject 注解来指明谁是依赖,这些依赖项必须是 public 或者 default 范围的,以便 dagger 可以给它们赋值。我们创建了一个 modules 数组(我们只有一个 module,DomainModule 是包含在 AppModule 里面的),并通过它来创建一个 ObjectGraph 实例。然后,我们手动注入 App 实例,这样之后,依赖就被注入了,因此可以直接调用 AnalyticsManager 的函数了。
结论
现在你掌握了 Dagger 的基础用法。ObjectGraph 和 Modules 是有效的使用 Dagger 必须掌握的最有意思的组件。还有其他更多的工具例如懒注入或者 provider 注入,在 Dagger官网上面有介绍,但在你熟练掌握本文所介绍的知识之前,我不建议你深入研究它们。
不要忘记托管在Github上面的 源代码 哦。
下一篇关于 Dagger 的文章将重点讨论 scoped object graphs。它主要包括创建只存活在创建者范围内的新对象图。为 activities 创建 scoped graphs 是很常见的。
文章链接:
[第一篇文章]:http://antonioleiva.com/dependency-injection-android-dagger-part-1/
[coffee makers]:https://github.com/square/dagger/tree/master/examples/simple
[示例工程]:https://github.com/JakeWharton/u2020
[Google Analytics]:https://www.google.com/analytics/
[Dagger官网]:http://square.github.io/dagger/
[源代码]:https://github.com/antoniolg/DaggerExample