使用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中使用。

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

[翻译] 原文地址

在这个系列中,我将向你解释什么是依赖注入,为什么使用依赖注入以及在Android中如何使用依赖注入框架dagger, dagger是square出品的专门为Android优化设计的依赖注入框架。这篇文章是是我上一篇关于如何在Android项目中使用MVP模式的姊妹篇,读者中一定有人对在同一个项目中结合MVP模式和dagger感兴趣,我认为这两者可以完美的结合在一起。

开始的部分可能有些偏理论。知道什么是依赖注入以及为什么会有依赖注入很重要,不然的话很可能会觉得我们费那么大劲引入依赖注入很不值得。

什么是依赖

想要注入依赖,必须要先弄懂什么是依赖。简而言之,依赖就是两个模块之间的耦合,常见的情况是一个模块需要使用另外一个模块来完成某些任务。

为什么存在依赖是很危险的

模块的依赖是很危险的,因为一旦我们想要替换其中的一个模块,我们必须要更改和要替换的模块存在耦合的模块。尤其是当我们想创建一个可以测试的应用的时候,单元测试通常要求被测试的模块是和其他模块隔离开的。测试中通常是将非测试模块mock掉(相当于用假的实现相同接口的模块去替换)。看看下面的一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Module1{
private Module2 module2;
public Module1(){
module2 = new Module2();
}
public void doSomething(){
...
module2.doSomethingElse();
...
}
}

我们如何去做到仅仅测试doSomething这个方法,而不用去关心doSomethingElse这个方法呢?如果测试失败了,那么又是哪个方法导致的呢?我们无法知道。如果doSomethingElse执行了保存到数据库相关的代码或者调用的其他的api,情况会变的更糟糕。

每一个new都是一个很难更改的依赖,我们应该尽可能的避免直接使用new。减少模块也不是一个避免过多依赖的好办法,别忘了单一职责原则(single responsibility principle)。

如何解决这个问题?依赖反转(dependency inversion)

既然不能使用new来直接创建依赖的对象,那就只好通过其他办法了。你猜是什么办法?yes,就是使用构造器来传入。这就是依赖反转的最基本的概念—不依赖具体的对象,依赖于抽象。
之前的那段代码可以改成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Module1{
private Module2 module2;
public Module1(Module2 module2){
this.module2 = module2
}
public void doSomething(){
...
module2.doSomethingElse();
...
}
}

那么到底什么是依赖注入呢?

你猜对了,通过构造器传入依赖的对象就是依赖注入,这样就不需要在一个模块中去创建另外一个模块了。对象是在别的地方被创建然后通过构造器传入依赖它的对象中。

但是新的问题又出现了,如果我不能在一模块中创建另外一个模块,总要有一个地方来负责实例化这些模块吧。而且,如果我们创建了一个有很长构造器参数列表模块,代码将会变的令人恶心难读。这正是依赖注入器(independency injector)要帮助我们解决的。

什么是依赖注入器?

你可以把他想象成一个这样的模块,它负责构造所有其他模块的实例,并且传入这些模块的依赖。现在我们的应用程序中只有一个入口来负责创建所有模块的实例,而这个入口完全在我们的控制下。

什么是Dagger

Dagger是一个完全为低端设备设计的依赖注入器。绝大多数的依赖注入器都是基于反射来实现的。反射的确很酷,但是在低端设备上执行反射的效率太低。Dagger使用的是预编译技术来创建所有依赖的类。这样就避免了使用反射。Dagger虽然没有其他的依赖注入框架强大,但它是效率最高的。

Dagger仅仅是为了测试而使用么?

当然不是。Dagger将会让在其他app里面重用你的模块变的更加简单,甚至在同一个应用中修改摸个模块也会变的更加容易。比如一个应用,在开发版本中使用本地文件来获取数据,而在正式版本中,使用API 服务获取数据,使用依赖注入在不同的模式下注入不同的对象将会很完美的解决这个问题。

小结

我知道这篇文章很晦涩,但是解释清楚下面几篇将会使用的术语很重要。我们已经知道了什么是依赖,使用依赖反转的好处以及如何使用一个依赖注入框架去实现它。

接下来的几篇文章,让我们尽情的coding,以解手痒吧!