Wonder4 Life

译 Dagger: dependency injection on Android (Part 2)

If you read first post about dependency injection, you will probably be looking for some real code. There are some beautiful examples about coffee makers at Dagger page, and an awesome model project by Jake Wharton for more experienced users. But we need something easier and coffee is not our main business model, so this article will provide an example where we are injecting some simple components to let us understand the basics.

Source code explained here can be found at DaggerExample repository at Github.

如果你阅读了有关依赖注入的第一篇文章,你可能在寻找相应的代码。这有一些漂亮的例子,如 Dagger 官方示例 coffee makers,以及一个很棒的适合更加有经验用户的例子 model project by Jake Wharton。但我们需要更简单的例子,并且 coffee 并不是我们的主营业务。因此,本文会提供一个示例,注入一些简单的组件,理解基础

源码在 DaggerExample repository at Github.

集成 Dagger 到项目中 (Include Dagger into your project)

There are two libraries that must be added if you want to use Dagger:

有两个库必须加到项目中:

更新到 Dagger 2.11

1
2
3
4
5
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.google.dagger:dagger:2.11'
annotationProcessor 'com.google.dagger:dagger-compiler:2.11'
}

First one is Dagger library. Second library is the dagger compiler. It will create required classes in order to be able to inject dependencies. That’s the way it can avoid most reflection, by creating precompiled classes. As we only need it to compile the project, and won’t be used by application, we mark it as provided so that it isn’t included in final apk.

第一个库是 Dagger 库。第二个库是 Dagger 编译器库,它会创建需要的类用于注入依赖。通过创建预编译的类,可以避免大部分的反射。

android gradle 插件版本 2.2.0 发布以后,提供了官方的 annotationProcessor 进行注解预编译,所以这部分不翻译了。相关历史可参考 What’s next for android-apt?

创建你的第一个模块 (Creating your first module)

Modules will be your daily job with dagger, so you need to feel comfortable with them. Modules are classes that provide instances of the objects we will need to inject. They are defined by annotating the class with @Module. There are some extra parameters that may be configured, but I’ll explain when we use them.

Create a class called AppModule, that will provide, for example, the Application Context. It’s usually interesting to have an easy access to it. I created App, which extends from Application, and added to the manifest.

Modules 是日常使用 dagger 经常要面对的,所以你需要和它们相处融洽。Modules 是一种类,用来提供需要注入的对象实例。它们通过 添加 @Module 注解来定义。还有一些额外参数需要配置,我将在用到时解释。

举个例子,创建一个 AppModule 类,用来提供 Application Context。方便的获取 Application Context 通常很有意义。我创建了 App 类继承于 Application,并添加到 manifest 中。

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

What is new here?

@Module : identify this class as a Dagger module.
injects : Classes where this module is going to inject any of its dependencies. We need to specify those classes that are directly injected into object graph. This will be covered soon.
@Provides: Identify method as an injection provider. The name of the method doesn’t matter, it only relies on what class type is provided.
@Singleton : if it’s present, the method will return always the same instance of the object, which is far better than regular singletons. If not, everytime this type is injected, we’ll get a new instance. In this case, as we are not creating a new instance, but returning an existing one, it would be the same if we don’t annotate as singleton, but it explains better what the provider is doing. Application instance is unique.

有什么新鲜的东西呢?

@Module : 标识当前类为 Dagger 模块。
injects : 注明这个模块将要注入的依赖的类。我们需要指明那些直接注入到对象图中的类,马上就会讲到。 这部分在 Dagger 2 中已经不再使用。
@Provides: 表示该方法提供注入的对象。方法名可以随意,它由提供的类所决定。
@Singleton : 如果出现 Singleton 注解,这个方法会始终返回相同的实例,这比常规的单例好。如果没有这个注解,每次注入这个类型,都会得到一个新的实例。在这个例子中,我们并没有创建新实例,而是返回已经存在的实例,与不增加 singleton 注解效果相同,但这样能够更好地标注这个提供者在做的事情:Application 实例是唯一的。

Why regular singletons are evil

Singletons are probably the most dangerous dependencies a project can have. First of all, because due to the fact that we are not creating an instance, it’s really hard to know where we are using it, so these are “hidden dependencies”. On the other way, we have no way to mock them for testing or to substitute it with another module, so our code becomes hard to maintain, to test and to evolve. Injected singletons, on the other way, have the benefits of a singleton (a unique instance) and, as we can create new instances at any moment, it’s easier to mock and to substitute with another piece of code, by subclassing or making them implement a common interface.

为何常规单例是有害的

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

We will be creating another module in a new package called domain. It’s quite useful to have (at least) a module in every architecture layer. This module will provide an analytics manager, that will throw an event when the app starts, only by showing a Toast. In a real project, this manager could call any analytics service such as Google Analytics.

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

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

By identifying this module as not complete, we say that some of the dependencies in this module need to be provided by another module. That’s the case of Application, which comes from AppModule. When we require this AnalyticsManager from a dependency injection, dagger will use this method, and will detect that it needs another dependency, Application, which will be requested to the object graph (almost there!). We also need to specify this module as a library, because dagger compiler will detect that AnalyticsManager is not been used by itself or its injected classes. It’s acting as a library module for AppModule.

We will specify that AppModule will include this one, so back to previous class:

通过标识这个 module 为未完成,表示这个 module 的一些依赖需要另外一个module来提供。即 AppModule 里面的 Application。当我们需要 AnalyticsManager 时,在依赖注入的过程中,dagger 将会使用这个方法,并会检测到它需要另外一个依赖:Application,然后会向对象图发起请求来获得。

我们同时需要指定 module 为 library,因为 dagger 编译器会检测到 AnalyticsMananger 没有被它自身或者它的注入类所使用。对于 AppModule 来说,DomainModule 就是一个库模块。

在 Dagger 2 的 modules 中,complete 和 library 不必指定,因为 Dagger 2 的 module 都被声明为 complete = falselibrary = true,

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

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

includes attribute is there for that purpose.

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

创建对象图 (Creating the Object Graph)

The object graph is the place where all these dependencies live. The object graph contains the created instances and is able to inject them to the objects we add to it.

In previous examples (AnalyticsManager) we have seen the “classic” dependency injection, where injections are passed via constructor. But we have some classes in Android (Application, Activity) where we don’t have control over constructor, so we simply need another way to inject its dependencies.

The combination of ObjectGraph creation and this direct injection is represented in App class. The main object graph is created in the Application class and it is injected in order to get its dependencies.

对象图是所有依赖对象生存的地方。对象图包含了创建的实例,并且可以注入到指定的对象中。

上一个例子中( AnalyticsManager ),我们见识了“经典”的依赖注入,通过构造函数产生依赖并注入。但是有一些 Android 的类( Application,Activity ),我们没办法控制他们的构造函数,所以我们需要另一种方式来注入他们.

在App类中,包含了对象图的创建和直接注入。对象图在Application类中被创建,并且为了获得它的依赖而进行注入.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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));
}
}

We specify dependencies by annotating them with @Inject. These fields must be public or default scoped, so that dagger can assign them. We create an array with our modules (we have only one, DomainModule is included in AppModule), and create an ObjectGraph with it. After it, we inject App instance manually. After that call, dependencies are injected, so we can call AnalyticsManager method.

我们通过@Inject注解来进行注入.注意这个域(属性)必须是 public 或者是 default 的,这样 dagger 才可以给它们赋值。我们还为这些模块(虽然例子中只有一个,DomainModule 是被包含进 AppModule 的)创建了数组,通过数组创建了对象图。之后,我们立即手动的进行了注入。在调用 objectGraph.inject(this) 之后,依赖就被注入了,我们就可以调用 AnalyticsManager 的方法了。

结论 (Conclusion)

Now you know the basics about Dagger. ObjectGraph and Modules are the most interesting components that must be mastered to use Dagger efficiently. There are some more tools such as lazy injections or provider injections that are explained at Dagger site, but I don’t recommend dive into them until you are fluent using what we saw here.

Don’t forget that source code is available at Github.

Next (and probably last) post about Dagger will be focused on scoped object graphs. It basically consists of creating new object graphs that lives only where its creator does. It’s common to create scoped graphs for activities.

现在你知道 Dagger 的基本使用了.对象图和 Module 是 dagger 最重要的组件,我们必须使用好它们,这样才能有效率的使用 dagger。还有一些类似于懒注入( azy injections )和提供注入( provider injections ),,在 Dagger site这里有解释.但我建议当你还没有流畅的使用 dagger 之前不要去钻研这些.

不要忘了在 Github上的例子源码.

接下来,可能是最后一篇关于 Dagger 的文章将集中讲解指定域的对象图表.就是有生命期的对象图表,一般用于在 Activity 中使用.

Reference

Dagger: dependency injection on Android (Part 2)

android studio 2.2 how to apply dagger2 without android-apt plugin

What’s next for android-apt?