Dagger2 入门 1

gy0801 8年前
   <p>这篇文章主要谈一下本人在学习Dagger2的心得,如有错漏,敬请谅解。</p>    <h2>什么是依赖注入</h2>    <p>依赖注入就是把下面这样的代码:</p>    <pre>  <code class="language-java">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();  }     </code></pre>    <p>变成:</p>    <pre>  <code class="language-java">class A {      A() {      }      }     class B {      A a;         B(A a) {          this.a = a;      }  }     class C {      A a;      B b;         C(A a, B b) {        this.a = a;        this.b = b;      }  }        main() {      A a = new A();      B b = new B(a);      C c = new C(a, b);  }     </code></pre>    <p>这种把对象之间的依赖生成的责任交给外界的做法,叫做依赖注入。</p>    <h2>如何更方便地进行依赖注入</h2>    <p>我们有类和它们之间的依赖关系,便很自然地会用图来表示这种状态。如上例子所示,可用下面这样一个图来表示:</p>    <pre>  <code class="language-java">          +-----+            |    |      +----> |  A  | <----+      |      |    |      |      |      +-----+      |      |                  |      |                  |  +---+---+          +---+---+  |      |          |      |  |  B  | <---------+  C  |  |      |          |      |  +-------+          +-------+     </code></pre>    <p>箭头表示依赖的对象。</p>    <p>我们想要这样的一种依赖注入框架:当我们需要一个B对象时,框架按照依赖遍历这个图,生成A,然后将其注入B,最后返回一个已经生成好的B对象。大概是:</p>    <p>B b = Injector.create(B.class)</p>    <p>另外,如果要求A对象是单例(这里不解释什么是单例)或对象的生成符合某种指定的规则,框架应自动识别并作出处理。</p>    <h2>设计框架</h2>    <p>我们面对两个主要问题:如何表示依赖图和如何生成对象。</p>    <h2>依赖图的表示</h2>    <p>我们需定义一种声明依赖的方法。可以用xml,json,甚至DSL来完成这个任务。这里我们采用比较流行和简便的注解(annotation)来表示依赖关系。</p>    <p>假设我们要的效果如下所示:</p>    <pre>  <code class="language-java">@dependence  class A {  }     @dependence(A.class)  class B {  }     @dependence({A.class, B.class})  class C {  }     </code></pre>    <p>可以看到,我们用@dependence注解来表示上面例图中的箭头。各个类之间的依赖关系十分清晰。</p>    <p>如果要求A是单例,我们可以这样:</p>    <pre>  <code class="language-java">@singleton  @dependence()  class A {  }     </code></pre>    <h2>对象生成</h2>    <p>建立了依赖图以后,需要通过某种方式生成我们需要的对象。我们希望是这样的:</p>    <pre>  <code class="language-java">B b = Injector.create(B.class)     </code></pre>    <p>或者通过注解实现自动注入</p>    <pre>  <code class="language-java">class Main {      @Inject      B b;         main() {          Injector.inject(this);          }      }     </code></pre>    <h2>Dagger2</h2>    <p>我们来看一下Dagger2是如何实现上述两个目标的。</p>    <h2>依赖图的表示</h2>    <p>Dagger2中,是通过@Inject注解或者@Module和@Provide这两个注解建立依赖图,如下所示:</p>    <p>首先定义好类:</p>    <pre>  <code class="language-java">public class A {      public 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;      }  }     </code></pre>    <p>然后我们用第一种方法来声明依赖:</p>    <pre>  <code class="language-java">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;      }  }     </code></pre>    <p>可以看到我们为每一个类的方法添加了@Inject声明,表示该类是依赖图中的一个节点。如果该初始化方法含有参数,那么这些从参数也应是依赖图中的节点。</p>    <p>第二种方法是通过一个module类来声明依赖,如下所示:</p>    <pre>  <code class="language-java">@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);      }  }     </code></pre>    <p>@Module注解表示这个ABCModule的作用是声明“依赖图”的。@Provides注解表示当前方法的返回值是图中的一个节点,方法的参数是依赖的对象,即前文中箭头指向的目标。</p>    <p>再强调一次,Dagger要求图中的每一个节点都要声明,即每一个节点都要在module中有@Provides注解的方法或者@Inject注解的初始化方法。</p>    <p>可以看到第二种方式(module)无需修改原来的对象。为了让模块尽量少地依赖第三方库,一般采用第二种方式来声明依赖图。</p>    <h2>对象生成</h2>    <p>Dagger2中,从依赖图中获取对象需通过component。component是依赖图和被注入对象之间的桥梁。如下所示:</p>    <pre>  <code class="language-java">@Component(module=ABCModule.class)  public interface ABCComponent {      public A provideA();         public B provideB();         public C provideC();         void inject(ActivitymainActivity);  }     </code></pre>    <p>@Component注解表示ABCComponent这个接口是一个Component。Component的方法隐含着如下两条规则:</p>    <ol>     <li> <p>不带参数的方法为“provider”方法,该方法的返回值是从依赖图中取得的对象。如下所示(伪代码):</p> <p>class Main {</p> <pre>  <code class="language-java">C c;     public void init() {      c = Component.provideC();  }     </code></pre> <p>}</p> </li>     <li> <p>带参数的方法,参数为“注入对象”。通常于@Inject标签同时使用。如下所示(伪代码):</p> <p>class Main {</p> <pre>  <code class="language-java">@Inject  C c;     public void init() {      Component.inject(this);  }     </code></pre> <p>}</p> </li>    </ol>    <p>即调用Component.inject(foorbar)的时候,框架自动为用@Inject标签标注的属性注入依赖。要求@Inject的属性的类必须是依赖图中的节点。</p>    <p>注意:component的方法必需至少符合以上两条规则中的一条。</p>    <p>注意:provider方法的名字一般为“provider <em>”,inject方法的名字一般为“inject</em> ”,但名字不影响这两个方法的功能。</p>    <p>当Component声明好以后,框架会在编译时生成一个Dagger <em>Component名字</em> 的类,我们可以用它来实施依赖注入,如下所示:</p>    <pre>  <code class="language-java">ABCComponentabcComponent = DaggerABCComponent.create();  A a = abcComponent.provideA();  B b = abcComponent.provideB();  C c = abcComponent.provideC();     </code></pre>    <p>或者:</p>    <pre>  <code class="language-java">class Main {         @Inject      A a;      @Inject      B b;      @Inject      C c;         public static void main() {          ABCComponentabcComponent = DaggerABCComponent.create();          abcComponent.inject(this);      }  }     </code></pre>    <p>Component标签的module属性可以是一个数组,即一个Component实施多个module的注入。引入类D和DModule:</p>    <pre>  <code class="language-java">class D {      public D() {      }      }     @Module  public class DModule {      @Provides      public D provideD() {          return new D();      }  }     </code></pre>    <p>修改ABCComponent,如下:</p>    <pre>  <code class="language-java">@Component(module={ABCModule.class, DModule.class})  public interface ABCComponent {      public A provideA();         public B provideB();         public C provideC();         public D provideD();         void inject(ActivitymainActivity);  }     </code></pre>    <p>如上即可实现D对象的注入。</p>    <h2>Component之间的依赖</h2>    <p>真正实施工程的时候,会将对象以功能分类。例如network相关,DB相关,Util相关的类集中在一起管理。Dagger2为方便我们达到这一个目的,在component中引入了dependence这个功能。</p>    <p>例如我们有如下component</p>    <pre>  <code class="language-java">@Component(modules = DModule.class)  public interface DComponent {      D provideD();  }     </code></pre>    <p>假设DComponent负责提供一个对象D。这种能力是项目无关的,我们把这个Component独立出来。然后我们可以通过@Component的dependence属性来为其他Component引入DComponent的能力。例如:</p>    <pre>  <code class="language-java">@Component(modules = ABCModule.class, dependencies = DComponent.class)  public interface ABCComponent {      A provideA();         B provideB();         C provideC();         D provideD();         void inject(Mainmain);  }     </code></pre>    <p>可以看到,声明了dependencies=DComponent.class以后,provideD方法可以顺利拿到D对象。inject方法也可以注入D对象。</p>    <pre>  <code class="language-java">public class Main {      @Inject      D d; // inject D by ABCComponent         public Main() {          DComponentdComponent = DaggerDComponent.create();          D d1 = dComponent.provideD(); // inject D by DComponent             ABCComponentabcComponent = DaggerABCComponent                  .builder()                  .dComponent(dComponent)                  .build();          D d2 = abcComponent.provideD();          abcComponent.inject(this);      }  }     </code></pre>    <p>DComponent不知道ABCComponent的存在,故可以像普通Component那样子使用。但在使用ABCComponent时,我们需要显式地为ABCComponent注入DComponent对象:</p>    <pre>  <code class="language-java">ABCComponentabcComponent = DaggerABCComponent          .builder()          .dComponent(dComponent)          .build();     </code></pre>    <h2>@Singleton</h2>    <p>如上面例子所示,如果要求D对象为单例,可以通过@Singleton注解来实现。首先我们需要在依赖图中声明对象是单例的:</p>    <pre>  <code class="language-java">@Module  public class DModule {      @Provides      @Singleton      public D provideD() {          return new D();      }  }     </code></pre>    <p>DComponent接口也需要声明:</p>    <pre>  <code class="language-java">@Singleton  @Component(modules = DModule.class)  public interface DComponent {      D provideD();  }     </code></pre>    <p>如此,当我们注入D对象时,可保证每次注入的是同一个D对象:</p>    <pre>  <code class="language-java">DComponentdComponent = DaggerDComponent.create();  D d1 = dComponent.provideD();   D d2 = dComponent.provideD();   // d1 == d2     </code></pre>    <h2>总结</h2>    <p>这篇文章只是简单地介绍了Dagger2的基本用法,下一篇打算讲Dagger2中的Scope和Subcomponent还在一些别的东西,敬请期待。</p>    <p> </p>    <p>来自:http://legendmohe.net/2016/08/20/android-dagger2-入门-1/</p>    <p> </p>