使用Android依赖注入工具Dagger(二)

[翻译]原文地址

如果你读过该系列的第一篇文章,那么你一定看看在真实的代码中如何去实现。Dagger官网有一个咖啡机的例子,同时Jake Wharton在github上面也提供了一个更棒的例子给更有经验的用户参考。但是我们需要一个稍微简单一些的例子,而咖啡机并不符合我们的业务场景,这篇文章将提供一个使用依赖注入的例子来帮助我们理解一些依赖注入的基本知识。

代码已经放到了github上面

在项目中使用Dagger

使用Dagger必须要添加两个类库到项目中:

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

第一个是Dagger的类库,第二个是Dagger的编译库。编译库负责生成相应的类,在这些生成的类中,所有的依赖已经被注入。通过使用预编译技术,Dagger避免了对反射的使用。编译库仅仅是在编译的时候使用,因此我们把它标记为provided,这样就不会在最终生成的apk文件中包含它。

创建第一个模块

使用Dagger的时候,模块(module)将会成为你最常用的部分,你必须习惯这一点。模块就是一个类,这个类负责提供那些需要被注入的对象。模块通过@Module注解来定义。还有一些参数可以配置,我会在用到的时候再详细解释。

创建一个叫AppModule的类,它将提供Application的实例。应用程序中会有很多地方使用application对象,因此使得这个对象容易获取会给开发带来很大的便利。例子中的App类继承自Application类,并且已经被添加到Manifest中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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的模块。

injects:那些需要这个模块注入他们的依赖的类。我们需要指定那些将被加入到Dagger的对象图中的类,后面会讲到这点。

@Provides:标识一个方法为注入的提供者。方法的名字可以随便写,但是返回值决定了它所能提供的类型。

@Singleton:一旦有了这个注解,那么这个方法将会永远返回相同的对象,这比常见的单例对象要好用的多。如果没有这个注解,每次注入这种类型的对象,都会得到一个全新的实例。在这个例子中,我们并没有在方法中创建一个新的对象,而是返回了一个已经存在的对象,即时我们不添加Singleton注解,该方法也会永远返回相同的实例,但是加上这个注解,可以增加代码的可读性。Application对象永远只会有一个。

我们将会的domain这个包下创建一个新的模块。在分层架构的没一层上都至少有一个模块会带来很多好处。这个模块会提供analytics manager对象。analytics manager对象会在应用启动的时候显示一个toast。在真实的项目中,这个manager对象可能会调用任何一个提供分析功能的服务比如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);
}
}

把这个模块标记为 not complete,是因为这个模块的一些依赖需要由其他的模块来提供。这里显然值得就是application对象,它需要由AppModule提供。当我们向Dagger请求AnalyticsManager的对象的时候,Dagger将会调用这个方法,同时会检测到这个方法依赖于另外一个模块,这时请求会被转发到对象图中。我们还把这个模块标记成library,因为Dagger编译器会检测到这个模块自己以及它所注入的类都没有使用AnalyticsManager,它仅仅是作为AppModule的一个库模块。

我们将会声明AppModule包含这个模块,之前的代码现在是这样:

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

includes的作用就是这样。

创建对象图(object graph)

对象图是存放所有依赖的地方。对象图包含了所有创建的实例,并且可以把这些实例注入到添加到对象图中的类中。

之前的例子中(AnalyticsManager),我们使用了经典的注入方式即构造函数注入。但是Android中有很多(Application, Activity),我们不能控制他们的构造函数,因此我们需要其他的方式来注入依赖。

App类中展示了如何将对象图和这种直接的依赖注入结合起来使用。主要的对象图在Application中被创建,同时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));
}
}

使用@Inject注解来标识依赖。依赖必须是public或者default scope的,这样Dagger才能对他们赋值。我们先创建了一个模块的数组(这里数组中只有一个模块,Do买呢Modu了被包含在AppModule中了),然后再使用这个数组去创建对象图,随后我们手动的注入了App的对象。所有这些调用完成之后,依赖就可以被注入了,我们可以放心的使用AnalyticsManager的方法了。

小结

现在你已经了解了Dagger。对象图(ObjectGraph)和模块(Module)是Dagger中最有意思的部分,想熟练使用Dagger的话必须要精通这两样。Dagger还提供了其他的一些工具比如延迟注入(lazy injection)和提供器注入(provider injection),请参考官网的介绍,但是在熟悉今天介绍的内容之前,我不推荐深入研究其他的。

代码

下一篇将主要讲解有作用域的对象图(scoped object graphs)。有作用域的对象图主要就是指那些只存在于创建者生命周期中的对象图。通常会在activity中使用。