[Android] Dagger2 入门 1

这篇文章主要谈一下本人在学习Dagger2的心得,如有错漏,敬请谅解。

什么是依赖注入

依赖注入就是把下面这样的代码:
class A {

    public A() {
    }

}

class B {

    A a;

    public B() {
        a = new A();
    }
}

class C {

    A a;
    B b;

    public C() {
        a = new A();
        b = new B();
        b.a = a;
    }

}


main() {
    C c = new C();
}
变成:
class A {

    }

class B {

    B(A a) {
        }

    }

class C {

    C(A a, B b) {
        }

    }


main() {

    A a = new A();
    B b = new B(a);
    C c = new C(a, b);

    }
这种把对象之间的依赖生成的责任交给外界的做法,叫做依赖注入。

如何更方便地进行依赖注入

我们有类和它们之间的依赖关系,便很自然地会用图来表示这种状态。如上例子所示,可用下面这样一个图来表示:
           +-----+
           |     |
    +----> |  A  | <----+
    |      |     |      |
    |      +-----+      |
    |                   |
    |                   |
+---+---+           +---+---+
|       |           |       |
|   B   | <---------+   C   |
|       |           |       |
+-------+           +-------+
箭头表示依赖的对象。 我们想要这样的一种依赖注入框架:当我们需要一个B对象时,框架按照依赖遍历这个图,生成A,然后将其注入B,最后返回一个已经生成好的B对象。大概是: B b = Injector.create(B.class) 另外,如果要求A对象是单例(这里不解释什么是单例)或对象的生成符合某种指定的规则,框架应自动识别并作出处理。

设计框架

我们面对两个主要问题:如何表示依赖图和如何生成对象。

依赖图的表示

我们需定义一种声明依赖的方法。可以用xml,json,甚至DSL来完成这个任务。这里我们采用比较流行和简便的注解(annotation)来表示依赖关系。 假设我们要的效果如下所示:
@dependence
class A {
}

@dependence(A.class)
class B {
}

@dependence({A.class, B.class})
class C {
}
可以看到,我们用@dependence注解来表示上面例图中的箭头。各个类之间的依赖关系十分清晰。 如果要求A是单例,我们可以这样:
@singleton
@dependence()
class A {
}

对象生成

建立了依赖图以后,需要通过某种方式生成我们需要的对象。我们希望是这样的:
B b = Injector.create(B.class)
或者通过注解实现自动注入
class Main {
    @Inject
    B b;

    main() {
        Injector.inject(this);
        }
    }

Dagger2

我们来看一下Dagger2是如何实现上述两个目标的。

依赖图的表示

Dagger2中,是通过@Inject注解或者@Module和@Provide这两个注解建立依赖图,如下所示: 首先定义好类:
public class A {
}

public class B {
    A a;

    public B(A a) {
        this.a = a;
    }
}

public class C {
    A a;
    B b;

    public C(A a, B b) {
        this.a = a;
        this.b = b;
    }
}
然后我们用第一种方法来声明依赖:
public class A {
    @Inject
    public A() {
    }
}

public class B {
    A a;

    @Inject
    public B(A a) {
        this.a = a;
    }
}

public class C {
    A a;
    B b;

    @Inject
    public C(A a, B b) {
        this.a = a;
        this.b = b;
    }
}
可以看到我们为每一个类的方法添加了@Inject声明,表示该类是依赖图中的一个节点。如果该初始化方法含有参数,那么这些从参数也应是依赖图中的节点。 第二种方法是通过一个module类来声明依赖,如下所示:
@Module
public class ABCModule {
    @Provides
    public A provideA() {
        return new A();
    }

    @Provides
    public B provideB(A a) {
        return new B(a);
    }

    @Provides
    public C provideC(A a, B b) {
        return new C(a, b);
    }
}
@Module注解表示这个ABCModule的作用是声明“依赖图”的。@Provides注解表示当前方法的返回值是图中的一个节点,方法的参数是依赖的对象,即前文中箭头指向的目标。 再强调一次,Dagger要求图中的每一个节点都要声明,即每一个节点都要在module中有@Provides注解的方法或者@Inject注解的初始化方法。 可以看到第二种方式(module)无需修改原来的对象。为了让模块尽量少地依赖第三方库,一般采用第二种方式来声明依赖图。

对象生成

Dagger2中,从依赖图中获取对象需通过component。component是依赖图和被注入对象之间的桥梁。如下所示:
@Component(module=ABCModule.class)
public interface ABCComponent {
    public A provideA();

    public B provideB();

    public C provideC();

    void inject(Activity mainActivity);
}
@Component注解表示ABCComponent这个接口是一个Component。Component的方法隐含着如下两条规则:
  1. 不带参数的方法为“provider”方法,该方法的返回值是从依赖图中取得的对象。如下所示(伪代码): class Main {
    C c;
    
    public void init() {
        c = Component.provideC();
    }
    
    }

  2. 带参数的方法,参数为“注入对象”。通常于@Inject标签同时使用。如下所示(伪代码): class Main {

    @Inject
    C c;
    
    public void init() {
        Component.inject(this);
    }
    
    }

即调用Component.inject(foorbar)的时候,框架自动为用@Inject标签标注的属性注入依赖。要求@Inject的属性的类必须是依赖图中的节点。 注意:component的方法必需至少符合以上两条规则中的一条。 注意:provider方法的名字一般为“provider”,inject方法的名字一般为“inject”,但名字不影响这两个方法的功能。 当Component声明好以后,框架会在编译时生成一个DaggerComponent名字的类,我们可以用它来实施依赖注入,如下所示:

ABCComponent abcComponent = DaggerABCComponent.create();
A a = abcComponent.provideA();
B b = abcComponent.provideB();
C c = abcComponent.provideC();
或者:
class Main {

    @Inject
    A a;
    @Inject
    B b;
    @Inject
    C c;

    public static void main() {
        ABCComponent abcComponent = DaggerABCComponent.create();
        abcComponent.inject(this);
    }
}
Component标签的module属性可以是一个数组,即一个Component实施多个module的注入。引入类D和DModule:
class D {
    public D() {
    }    
}

@Module
public class DModule {
    @Provides
    public D provideD() {
        return new D();
    }
}
修改ABCComponent,如下:
@Component(module={ABCModule.class, DModule.class})
public interface ABCComponent {
    public A provideA();

    public B provideB();

    public C provideC();

    public D provideD();

    void inject(Activity mainActivity);
}
如上即可实现D对象的注入。

Component之间的依赖

真正实施工程的时候,会将对象以功能分类。例如network相关,DB相关,Util相关的类集中在一起管理。Dagger2为方便我们达到这一个目的,在component中引入了dependence这个功能。 例如我们有如下component
@Component(modules = DModule.class)
public interface DComponent {
    D provideD();
}
假设DComponent负责提供一个对象D。这种能力是项目无关的,我们把这个Component独立出来。然后我们可以通过@Component的dependence属性来为其他Component引入DComponent的能力。例如:
@Component(modules = ABCModule.class, dependencies = DComponent.class)
public interface ABCComponent {
    A provideA();

    B provideB();

    C provideC();

    D provideD();

    void inject(Main main);
}
可以看到,声明了dependencies=DComponent.class以后,provideD方法可以顺利拿到D对象。inject方法也可以注入D对象。
public class Main {
    @Inject
    D d; // inject D by ABCComponent

    public Main() {
        DComponent dComponent = DaggerDComponent.create();
        D d1 = dComponent.provideD(); // inject D by DComponent

        ABCComponent abcComponent = DaggerABCComponent
                .builder()
                .dComponent(dComponent)
                .build();
        D d2 = abcComponent.provideD();
        abcComponent.inject(this);
    }
}
DComponent不知道ABCComponent的存在,故可以像普通Component那样子使用。但在使用ABCComponent时,我们需要显式地为ABCComponent注入DComponent对象:
ABCComponent abcComponent = DaggerABCComponent
        .builder()
        .dComponent(dComponent)
        .build();

总结

这篇文章只是简单地介绍了Dagger2的基本用法,下一篇打算讲Dagger2中的Scope和Subcomponent还在一些别的东西,敬请期待。

查看原文:http://legendmohe.net/2016/08/20/android-dagger2-%e5%85%a5%e9%97%a8-1/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值