Dagger2 系列(二)Dagger2 进阶使用

通过前面一节的介绍,我们学习了关于Dagger2的一些基本概念和简单使用方法,对Dagger2有了一个初步的认识。而对于我们在工程中的实际使用来说,掌握基本用法是远远不够的,接下来我们继续介绍Dagger2的进阶用法。

在介绍Dagger2进阶用法之前,我们先回顾一下使用Dagger2的几个关键步骤: 首先目标类里要使用 Inject表示出自己需要进行注入的对象;然后需要在依赖对象类中使用 Inject来标识构造方法,或者在Module中使用 Provides 标识对象的生成方法。最后需要一个 Component作为桥梁将生成的对象赋值给目标类的依赖成员。由此可见,Dagger中最关键的三要素如下图所示,后续将要介绍的进阶用法也是围绕着这三要素来展开的:


Dagger2三要素

我们在实际的工程开发中,目标类对于依赖对象的需求是多种多样的,比如很多类有多个构造方法,如下代码示例,应该使用哪个构造方法以及如何标识不同的构造方法呢,这个就是接下来将要介绍的 Qualifier 标识符所起的作用。

@Module
public class BodyModule {

    @Provides
    public Body provideBody() {
        return  new Body();
    }

    @Provides
    public Body provideBody(Leg leg) {
        return  new Body(leg);
    }
    
    @Provides
    public Leg provideLeg() {
        return new Leg();
    }
}

Qualifier 是元注解,也就是注解的注解,用来定义不同的注解名字来对不同的构造方法进行区分,如下代码所示,我们使用Qualifier标注了两个注解,分别为ProvideBody ,ProvideNewBody :

@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface ProvideBody {
}
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface ProvideNewBody {
}

我们需要将上述定义的两个注解分别标识在 Module中两个构造方法上,以此来标识两个不同的构造方法。另外需要在目标类依赖实例的地方标识需要使用哪个构造方法,这样就能使用指定的方法来创建依赖实例了,Qualifier的使用还是比较简单的,代码如下示例:

@Module
public class BodyModule {

    @Provides
    @ProvideBody
    public Body provideBody() {
        return  new Body();
    }

    @Provides
    @ProvideNewBody
    public Body provideNewBody(Leg leg) {
        return  new Body(leg);
    }

    @Provides
    public Leg provideLeg() {
        return new Leg();
    }
}

public class People {
    @Inject
    @ProvideBody
    Body mBody;

    public People() {
    }
}

假如在目标类中依赖的对象要求是单例的,在一定的生命周期内使用同一个对象,使用Dagger2应该如何做呢。根据之前基础使用方法中的介绍,每次我们调用 component 的 inject方法时,都会新创建一个对象来注入。如果我们想使用一个实例,那么就需要在创建了一个实例之后,后续每次使用都返回同一个对象而不是重新创建。如何达到这一目的呢,这里就需要用到 Scope 注解 。Scope 顾名思义是作用域,用于标注一个对象的作用域。Scope也是一个元注解,首先用Scope 来定义一个注解:

@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface PeopleScope {
}

然后将这个定义好的注解 PeopleScope 标注在 Component 以及 Module 的构造方法上。

@Module
public class BodyModule {

    @Provides
    @ProvideBody
    @PeopleScope
    public Body provideBody() {
        return  new Body();
    }
    .....
}

@Component(modules = BodyModule.class)
@PeopleScope
public interface PeopleComponent {
    void injectPeople(People people);
}

这样标注之后和之前有什么区别呢,这里就需要我们来看Dagger生成的DaggerPeopleComponent源码了,为了精简篇幅,这里我们只截取核心的源码部分

public final class DaggerPeopleComponent implements PeopleComponent {
  private Provider<Body> provideBodyProvider;

  @SuppressWarnings("unchecked")
  private void initialize(final Builder builder) {
    this.provideBodyProvider =
        DoubleCheck.provider(BodyModule_ProvideBodyFactory.create(builder.bodyModule)); // 通过DoubleCheck进行一次包装
  }
  @Override
  public void injectPeople(People people) {
    injectPeople2(people);
  }

  private People injectPeople2(People instance) {
    People_MembersInjector.injectMBody(instance, provideBodyProvider.get());
    return instance;
  }
  ......
  
  }

通过这段核心代码,我们看到生成的 DaggerPeopleComponent 和之前不同的地方是调用了 DoubleCheck.provider 方法对provider进行了包装,那么 DoubleCheck 做了什么工作呢,继续看DoubleCheck的源码:

public final class DoubleCheck<T> implements Provider<T>, Lazy<T> {
    private static final Object UNINITIALIZED = new Object();
    private volatile Provider<T> provider;
    private volatile Object instance;

    private DoubleCheck(Provider<T> provider) {
        this.instance = UNINITIALIZED;
        assert provider != null;
        this.provider = provider;
    }

    public T get() {   // 实现单例
        Object result = this.instance;
        if(result == UNINITIALIZED) {
            synchronized(this) {
                result = this.instance;
                if(result == UNINITIALIZED) {
                    result = this.provider.get();
                    this.instance = reentrantCheck(this.instance, result);
                    this.provider = null;
                }
            }
        }

        return result;
    }
.......
}

看到这段代码,想必大家已经快明白了,这个get方法不就是单例的写法么,如果已经创建过一次实例,后续每次调用get方法,返回的都是第一次创建的实例。如此在 DaggerPeopleComponent 中每次调用inject方法时,都会调用到这个get方法,从而达到使用同一实例的目的。另外强调一句,这里的单例只是对于同一个 Component对象来说的,在同一个 Component对象的生命周期里,持有的依赖对象为同一个。不同的Component对象中的自然就是不同的了。另外强调一下使用Scope的注意事项:Module 中 provide 方法的 Scope 注解和 与之绑定的 Component 的 Scope 需要保持一致,否则作用域不同会导致编译时会报错。

听到这里可能还存在很多疑问,每个目标类依赖都有对应的 Component类,每个Component对象都持有相应的依赖对象,如果某个Component对象希望和另外一个Component对象共享依赖实例,应该如何做呢,接下来我们学习Component的组织关系,用于解决Component。Component拥有两种关系依赖关系和继承关系:一个Component可以依赖于一个或多个Component,依赖关系通过Component中的dependencies属性来实现,具体用法通过一个例子来看一下;

本例中 People 和 Animal中都要依赖一个 Body类型的对象,而且Animal 要和People 共用同一个Body对象。首先要在 PeopleComponent中增加一个 提供Body对象的接口,在AnimalComponent 中要使用dependencies 属性标明 依赖 PeopleComponent , 最后注意 PeopleComponent 和 AnimalComponent都需要标注 Scope,而且两者的Scope不能相同。

@Component(modules = BodyModule.class)
@PeopleScope
public interface PeopleComponent {
    void injectPeople(People people);

    Body getBody();
}

@Component(dependencies = PeopleComponent.class)
@AnimalScope
public interface AnimalComponent {
    void inject(Animal animal);
}

然后编译一下,来详看生成的DaggerAnimalComponent 中的代码,出于篇幅考虑仅贴出部分核心代码,首先我们看到Builder类中增加了一个成员 PeopleComponent, 用于传入PeopleComponent 实例,在inject方法中,获取依赖实例时调用的是传入的peoplecomponent的get方法,因此AnimalComponent 和 PeopleComponent共享了同一个 Body的实例:

public final class DaggerAnimalComponent implements AnimalComponent {
  private PeopleComponent peopleComponent;

  ......
  @Override
  public void inject(Animal animal) {
    injectAnimal(animal);
  }

  private Animal injectAnimal(Animal instance) {
    Animal_MembersInjector.injectMBody(
        instance,
        Preconditions.checkNotNull(
            peopleComponent.getBody(), "Cannot return null from a non-@Nullable component method"));
    return instance;
  }

  public static final class Builder {
    private PeopleComponent peopleComponent;

    private Builder() {}

    public AnimalComponent build() {
      if (peopleComponent == null) {
        throw new IllegalStateException(PeopleComponent.class.getCanonicalName() + " must be set");
      }
      return new DaggerAnimalComponent(this);
    }

    public Builder peopleComponent(PeopleComponent peopleComponent) {
      this.peopleComponent = Preconditions.checkNotNull(peopleComponent);
      return this;
    }
  }
}

使用方法如下所示
PeopleComponent peopleComponent = DaggerPeopleComponent.builder().build();
AnimalComponent animalComponent = DaggerAnimalComponent.builder().peopleComponent(peopleComponent).build();
animalComponent.inject(this);

上面介绍的是Component 的依赖关系,Component还有一种继承关系,也可达到 Component之间共享依赖实例的目的。如果使AnimalComponent继承PeopleComponent,使用方式如下,继承关系的使用相对复杂一些。首先 使用 SubComponent标注AnimalComponent,AnimalComponent 中增加一个 Builder接口并用@Subcomponent.Builder标注。PeopleComponent中增加一个方法返回类型为 AnimalComponent.Builder,用于外部创建AnimalComponent 对象使用。最后要在PeopleComponent所使用的module中标识 subcomponents = AnimalComponent.class,自此就建立起来 AnimalComponent 和 PeopleComponent之间的继承关系:

@Component(modules = BodyModule.class)
@PeopleScope
public interface PeopleComponent {
    void injectPeople(People people);

    AnimalComponent.Builder animalComponent();
}

@Module(subcomponents = AnimalComponent.class)
public class BodyModule {

    @Provides
    @PeopleScope
    public Body provideBody() {
        return  new Body();
    }
}

@Subcomponent
@AnimalScope
public interface AnimalComponent {
    void inject(Animal animal);
    @Subcomponent.Builder
    interface Builder {
        AnimalComponent build();
    }
}

我们还是编译一下看生成的核心源码, 这里看到有个区别,只生成了 DaggerPeopleComponent 类,没有生成 DaggerAnimalComponent类。创建AnimalComponent 对象的方法在 DaggerPeopleComponent 中,DaggerPeopleComponent中的AnimalComponentImpl 是AnimalComponent 接口的实现类,其中的injectAnimal方法 注入的对象是由DaggerPeopleComponent 中的provideBodyProvider提供的,由此实现了 AnimalComponent和PeopleComponent共享依赖实例的目的。

public final class DaggerPeopleComponent implements PeopleComponent {
  private Provider<Body> provideBodyProvider;
  ......

  public static PeopleComponent create() {
    return new Builder().build();
  }

  @Override
  public void injectPeople(People people) {
    injectPeople2(people);
  }

  @Override
  public AnimalComponent.Builder animalComponent() {
    return new AnimalComponentBuilder();
  }

  private People injectPeople2(People instance) {
    People_MembersInjector.injectMBody(instance, provideBodyProvider.get());
    return instance;
  }

  private final class AnimalComponentBuilder implements AnimalComponent.Builder {
    @Override
    public AnimalComponent build() {
      return new AnimalComponentImpl(this);
    }
  }

  private final class AnimalComponentImpl implements AnimalComponent {
    private AnimalComponentImpl(AnimalComponentBuilder builder) {}

    @Override
    public void inject(Animal animal) {
      injectAnimal(animal);
    }

    private Animal injectAnimal(Animal instance) {
      Animal_MembersInjector.injectMBody(
          instance, DaggerPeopleComponent.this.provideBodyProvider.get());
      return instance;
    }
  }
}

上面介绍了 Component 组织关系的两种方式,在APP的开发中,有非常多的类都有依赖对象,总不能每一个目标类都配一个Component吧。应该以什么样的原则来划分Component呢,Component应该划分为多小的粒度呢,一般会遵循如下的组织原则:

  1. 创建一个全局的Component(ApplicationComponent), 在application中对其进行实例化,一般会在这个component中用来管理APP中的全局类实例。

  2. 对于每个页面创建一个Component,一个Activity页面定义一个Component,一个Fragment定义一个Component,使这些component继承自applicationComponent。
    这部分内容可以参考一下 实例,https://github.com/googlesamples/android-architecture/tree/todo-mvp, 这个实例中展示了如何在APP中使用Dagger2,在此不再展开详述。
    通过上面对Dagger2进阶用法的介绍,可能大家心中依然存在一些疑惑,从入门到这里有点想放弃。Dagger2的使用需要在每个目标类里重复写大量的模板代码,这与Dagger2解耦,减少重复劳动的目标是有一定背离的。如何在APP开发中更加简洁,方便的使用Dagger2呢,接下来将要继续介绍的Dagger-android框架,将会继续详细阐述这一问题的解决方法,使Dagger2在APP开发中的使用更加简洁。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容