Android中的依赖注入:Dagger函数库的使用(二)

原文链接:http://antonioleiva.com/dagger-android-part-2/

如果你阅读了关于依赖注入的第一篇文章,你可能期待看到真实的代码。在Dagger网页上面可以找到coffee makers优美的例子,以及由Jake Wharton创建的,更适用于有经验读者的很棒的示例工程。但我们需要更简单的例子,coffee makers并不是我们主要的业务模式。因此,本文会提供简单的例子,展示注入简单的组件,让我们理解基本知识。

本文介绍的例子源码可以在这个页面找到https://github.com/antoniolg/DaggerExample;

在工程中引入Dagger

如果想使用Dagger的话,需要添加两个函数库:

  1. dependencies {
  2. compile fileTree(dir: 'libs', include: ['*.jar'])
  3. compile 'com.squareup.dagger:dagger:1.2.+'
  4. provided 'com.squareup.dagger:dagger-compiler:1.2.+'
  5. }

第一个是Dagger函数库,第二个是Dagger编译器函数库,它会创建注入依赖所需的类。通过创建预编译的类可以避免大部分的反射操作。由于我们只需要Dagger编译器函数库来编译工程,在应用中不会使用到它,因此我们把它标记为provided,这样在最终的apk中就不会包含这个函数库了。

创建第一个module

Modules是使用Dagger经常碰到的概念,所以你需要习惯它们。Modules是提供依赖注入时所需对象实例的类,它们通过@Module注解来修饰类。还有其他一些额外参数可能需要配置,但我们会在使用到的时候再进行介绍。

创建一个名为AppModule的类,假设用于提供Application Context。为Application Context提供简单的访问入口通常是有趣的。我创建继承自Application的类App,并添加到AndroidManifest文件中。

  1. @Module(
  2. injects = {
  3. App.class
  4. }
  5. )
  6. public class AppModule {
  7. private App app;
  8. public AppModule(App app) {
  9. this.app = app;
  10. }
  11. @Provides @Singleton public Context provideApplicationContext() {
  12. return app;
  13. }
  14. }

上面涉及到哪些新知识呢?

  • @Module:把这个类标识为Dagger module。injects:标识module将要注入这个类的任何依赖。我们需要明确指定将直接注入到对象图中的那些类,这将很快会讲到。
  • @Providers:标识函数作为注入提供者,函数名并不重要,它只依赖于所提供的类类型。
  • @Singleton:如果标识为Singleton,那这个函数会一直返回相同的对象实例,这比常规的单例好很多。否则,每次注入类型都会得到一个新的实例。在这个例子中,由于我们没有创建新实例,而是返回已经存在的实例,因此即使不把函数标识为Singleton,每次调用还是会返回相同的实例的,但这样能够更好地说明提供者到底做了什么。Application实例是唯一的。

    为什么常规的单例是不好的

单例可能是一个工程能够具有的最危险的依赖了。首先,由于我们没有创建实例,因此很难知道要在什么地方使用它,因此具有“隐藏依赖”。另外,我们没有办法通过mock来测试它,或者以另外的模块替换它,因此代码变得难以维护,测试和进化。被注入的单例,相反,具有单例的优势(唯一的实例),同时,由于我们可以在任何时候创建新的实例,因此很容易mock或者通过子类化或者使其实现一个公有接口来使用另一个代码片段替换它。

我们将在一个新的名为domain的包里面创建另外一个module。在每个架构层拥有至少一个module是很有用的。这个module将提供一个统计管理器,在app启动时通过显示一个Toast来抛出一个事件。在实际的工程中,这个管理器可能调用任何统计服务例如Google Analytics

  1. @Module(
  2. complete = false,
  3. library = true
  4. )
  5. public class DomainModule {
  6. @Provides @Singleton public AnalyticsManager provideAnalyticsManager(Application app){
  7. return new AnalyticsManager(app);
  8. }
  9. }

通过把这个module指明为未完成,表示这个module的某些依赖需要另外一个module来提供。也就是AppModule里面的Application。当我们从一个依赖注入请求这个AnalyticsManager时,dagger将会使用这个函数,并会检测到它需要另外一个依赖:Application,这会通过向对象图发起请求来获得。我们同时需要把module指明为library,因为dagger编译器会检测到AnalyticsMananger没有被它自身或者它的注入类所使用。对于AppModule来说,DomainModule就是一个库模块。

我们将指定AppModule会包含DomainModule,因此返回AppModule类,修改代码如下:

  1. @Module(
  2. injects = {
  3. App.class
  4. },
  5. includes = {
  6. DomainModule.class
  7. }
  8. )
  9. public class AppModule {
  10. ...
  11. }

includes属性正是用来实现这个目的的。

创建对象图

对象图是所有依赖存在的地方。对象图包含被创建的实例,并能够把这些实例注入到相应的对象中。

在前面的例子中(AnalyticsManager)我们看到了经典的依赖注入,注入是通过构造函数参数传递的。但在Android中有的类(Application,Activity)我们对构造函数没有控制权,因此我们需要另外一种方式来注入依赖。

ObjectGraph创建和直接注入的组合方式在App类中可以看到。主对象图在Application类中创建并被注入以获得依赖。

  1. public class App extends Application {
  2. private ObjectGraph objectGraph;
  3. @Inject AnalyticsManager analyticsManager;
  4. @Override public void onCreate() {
  5. super.onCreate();
  6. objectGraph = ObjectGraph.create(getModules().toArray());
  7. objectGraph.inject(this);
  8. analyticsManager.registerAppEnter();
  9. }
  10. private List<Object> getModules() {
  11. return Arrays.<Object>asList(new AppModule(this));
  12. }
  13. }

我们通过@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是很常见的。

原文: https://asce1885.gitbooks.io/android-rd-senior-advanced/content/androidzhong_de_yi_lai_zhu_ru_ff1a_dagger_han_shu_ku_de_shi_yong_ff08_er_ff09.html