Why Dagger

2020-07-15  本文已影响0人  朱兰婷

Dagger如何工作

不使用Dagger的示例

如果现在要写这么一个代码:我们的主宰大人最近想玩一个游戏:创造一个个人,给其中某些人分配灵魂,然后把他们随机放到“悲惨世界”和“快乐星球”去渡劫一场人生。如图


Master game.png

这里用到工厂模式和依赖注入,如下

  1. 创建人类肉体的女娲大人:
class Nuwa {

    public Person getWoman(){
        Face face = new Face(new Eye(), new Nose());
        Head head = new Head(face, new Hair("long"));
        return new Woman(new Hand(), head);
    }

    public Person getMan(){
        Face face = new Face(new Eye(), new Nose());
        Head head = new Head(face, new Hair("short"));
        return new Man(new Hand(), head);
    }
}

  1. 创建信念的工厂FaithFactory:
class FaithFactory {

    public Faith believeInEvil(){
        return new Evil();
    }

    public Faith believeInMerciful(){
        return new Merciful();
    }
}
  1. 创建追求的造梦厂DreamMaker:
class DreamMaker {

    public Pursuit pursueMoney(){
        return new Money();
    }

    public Pursuit pursuePower(){
        return new Power();
    }
}
  1. SoulFactory:
class SoulFactory {

    private Pursuit mMoneyPursuit;
    private Pursuit mPowerPursuit;
    private Faith mEvilFaith;
    private Faith mMercifulFaith;

    public SoulFactory(){
        DreamMaker dreamMaker = new DreamMaker();
        mMoneyPursuit = dreamMaker.pursueMoney();
        mPowerPursuit = dreamMaker.pursuePower();

        FaithFactory faithFactory = new FaithFactory();
        mEvilFaith = faithFactory.believeInEvil();
        mMercifulFaith = faithFactory.believeInMerciful();
    }

    public Soul createMoneyEvilSoul() {
        return new Soul(mMoneyPursuit, mEvilFaith);
    }
...
}
  1. 开天辟地的盘古先生:
class Pangu {

    public Universe getLesMiserable(){
        return new LesMiserable();
    }

    public Universe getHappyPlanet(){
        return new HappyPlanet();
    }
}

然后,我们的主宰大人在玩游戏时是这样的:

public class Master {

    public void playGame(){
        Nuwa nuwa = new Nuwa();
        Pangu pangu = new Pangu();
        SoulFactory soulFactory = new SoulFactory();

        Soul soul = soulFactory.createMoneyEvilSoul();
        Person person = nuwa.getMan();
        person.updateSoul(soul);
        Universe universe = pangu.getLesMiserable();
        universe.welcome(person);
        person.nirvana();
    }
}

从上可以看到,这段代码有两个令人烦闷的点:

  1. 需要手动new一堆类。
  2. 里面有4个factory模版:Nuwa、Pangu、FaithFactory、DreamMaker。

Dagger的作用就是解决这两个问题,让你不需要手动new一堆类,也不用构建一堆factory模版。


使用javax.inject.Inject + dagger.Component

  1. 在类的构造函数前加上@Inject注释,告诉Dagger如何创造这个类:
    @Inject
    public Woman(@NonNull Hand hand, @NonNull Head head) {
    }
  1. 构造函数的参数类的构造函数前也要加上@Inject注释,告诉Dagger如何创造这个类,一直嵌套,直至构造函数没有参数:
    @Inject
    public Head(@NonNull Face face, @NonNull Hair hair){
    }

    @Inject
    public Face(@NonNull Eye eye, @NonNull Nose nose){
    }

    @Inject
    public Eye(){
    }

如果某个构造函数的参数需要外部提供怎么办呢?请参考“使用dagger.Module + dagger.Provides”。

  1. 创建一个interface类,并为它加上@Component注释,通常这个类我们按xxxComponent的规范来命名。如下:
@Component(modules = {MasterGameModule.class, SoulModule.class})
interface MasterGameComponent {

    // create Person
    Man getMan();
    Woman getWoman();

    SoulFactory getSoulFactory();

    // create Universe
    HappyPlanet getHappyPlanet();
    LesMiserable getLesMiserable();
}
  1. 以上步骤之后,build project,编译器会自动生成一个类似Nuwa的Factory类,如下
final class DaggerMasterGameComponent implements MasterGameComponent {
  private DaggerMasterGameComponent() {

  }

  public static Builder builder() {
    return new Builder();
  }

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

  private Face getFace() {
    return new Face(new Eye(), new Nose());}

  private Hair getHair() {
    return new Hair(MasterGameModule_ProvideLongHairFactory.provideLongHair());}

  private Head getHead() {
    return new Head(getFace(), getHair());}

  @Override
  public Man getMan() {
    return new Man(new Hand(), getHead());}

  @Override
  public Woman getWoman() {
    return new Woman(new Hand(), getHead());}

  @Override
  public SoulFactory getSoulFactory() {
    return new SoulFactory(SoulModule_ProvidePursueMoneyFactory.providePursueMoney(), SoulModule_ProvidePursuePowerFactory.providePursuePower(), SoulModule_ProvideEvilFactory.provideEvil(), SoulModule_ProvideMercifulFactory.provideMerciful());}

  @Override
  public HappyPlanet getHappyPlanet() {
    return new HappyPlanet();}

  @Override
  public LesMiserable getLesMiserable() {
    return new LesMiserable();}

  static final class Builder {
    private Builder() {
    }

    public MasterGameComponent build() {
      return new DaggerMasterGameComponent();
    }
  }
}

可以看到这个类自动创建了构造Person、Soul、Universe类所需要的所有依赖类如Head、HappyPlanet之类,这样在new一个类时可以自动找到其依赖对象的构造函数,如此嵌套直到构造类成功。

  1. Master直接使用DaggerMasterGameComponent代替Nuwa、Pangu等Factory,如下:
public class Master {

    public void playGameWithDagger(){
        MasterGameComponent masterGameComponent = DaggerMasterGameComponent.create();

        Soul soul = soulFactory.createMoneyEvilSoul();
        Person person = masterGameComponent.getMan();
        person.updateSoul(soul);
        Universe universe = masterGameComponent.getLesMiserable();
        universe.welcome(person);
        person.nirvana();
    }
}

从上面可以看到,Dagger自动构建了Factory模版且自动生成inject类的构造函数,使得程序开发者不需要手动构建Factory模版。


使用dagger.Module + dagger.Provides

使用 @Provides 告知 Dagger 如何提供您的项目所不具备的类。

上面的demo中,Hair类的构造函数有一个String类型的参数,在默认情况下Dagger是无法自动构建这个带了参数的构造函数的。这个时候要使用@Provides注释告诉Dagger如何提供项目本身无法提供的类。@Provides注释要放在带有@Module注释的类中,一般来说,我们约定带有@Module注释的类以xxxModule命名,带有@Provides注释的方法以provideXXX()命名。如:

@Module
abstract class MasterGameModule {

    @Provides
    public static String provideLongHair(){
        return "long";
    }
}

然后在你的@Componect注释接口上加上

@Component(modules = MasterGameModule.class)

这样Dagger会为Module中每个Provides的方法创建一个以MasterGameModule_ProvideXXXFactory.java类,如下:

public final class MasterGameModule_ProvideLongHairFactory implements Factory<String> {
...
  public static String provideLongHair() {
    return Preconditions.checkNotNull(MasterGameModule.provideLongHair(), "Cannot return null from a non-@Nullable @Provides method");
  }
...
}

它调用了xxxModule.provideXXX()方法并供DaggerXXXComponent使用:

final class DaggerMasterGameComponent implements MasterGameComponent {
...
  private Hair getHair() {
    return new Hair(MasterGameModule_ProvideLongHairFactory.provideLongHair());}
...
}

使用dagger.BindsInstance

如果我们不想用Module提供Hair的参数,而是想在构建时动态传入Hair的参数怎么办呢:

@Component(modules = MasterGameModule.class)
@MasterGameAnnotations.MasterGameScope
interface MasterGameComponent {

    ...
    @Component.Builder
    interface Builder{
        @BindsInstance
        Builder hairLength(String hairLength);
        MasterGameComponent build();
    }
}

给MasterGameComponent指定一个Builder接口,在使用接口时传入hairLength参数。

之后,build project,得到的DaggerMasterGameComponent中将多一个hairLength(BindsInstance注释的方法名)的变量:

final class DaggerMasterGameComponent implements MasterGameComponent {
  private final String hairLength;
  private DaggerMasterGameComponent(String hairLengthParam) {
    this.hairLength = hairLengthParam;
    initialize(hairLengthParam);
  }
...
  private Hair getHair() {
    return new Hair(hairLength);}

  private static final class Builder implements MasterGameComponent.Builder {
    private String hairLength;

    @Override
    public Builder hairLength(String hairLength) {
      this.hairLength = Preconditions.checkNotNull(hairLength);
      return this;
    }

    @Override
    public MasterGameComponent build() {
      Preconditions.checkBuilderRequirement(hairLength, String.class);
      return new DaggerMasterGameComponent(hairLength);
    }
  }
}

这时如果要指定Hair参数并获取MasterGameComponent的方法为:

MasterGameComponent masterGameComponent = DaggerMasterGameComponent.builder().hairLength("long").build();

使用javax.inject.Qualifier

上面的SoulFactory在创建Faith和Pursuit时用了他们的工厂类,如果现在用Dagger没有Faith和Pursuit类的工厂类了,怎么办呢?

  1. 定义@Qualifier注释来区分不同类型的Faith和Pursuit。
class MasterGameAnnotations {

    @Qualifier
    public @interface Money {}

    @Qualifier
    public @interface Power{}

    @Qualifier
    public @interface Evil{}

    @Qualifier
    public @interface Merciful{}
}

2.定义一个Module,提供不同类型的Faith和Pursuit:

@Module
abstract class SoulModule {

    @Provides
    @MasterGameAnnotations.Money
    public static Pursuit providePursueMoney() {
        return DaggerSoulComponent.create().getMoney();
    }

    @Provides
    @MasterGameAnnotations.Power
    static Pursuit providePursuePower() {
        return DaggerSoulComponent.create().getPower();
    }

    @Provides
    @MasterGameAnnotations.Evil
    public static Faith provideEvil() {
        return DaggerSoulComponent.create().getEvil();
    }

    @Provides
    @MasterGameAnnotations.Merciful
    public static Faith provideMerciful() {
        return DaggerSoulComponent.create().getMerciful();
    }
}
  1. 定义代@Inject的SoulFactory的构造函数:
    @Inject
    public SoulFactory(@MasterGameAnnotations.Money Pursuit money, @MasterGameAnnotations.Power Pursuit power,
                       @MasterGameAnnotations.Evil Faith evil, @MasterGameAnnotations.Merciful Faith merciful) {
        mMoneyPursuit = money;
        mPowerPursuit = power;
        mEvilFaith = evil;
        mMercifulFaith = merciful;
    }

这样,Master在使用时就可以用:

Soul soul = masterGameComponent.getSoulFactory().createMoneyEvilSoul();

使用javax.inject.Singleton & javax.inject.Scope

@Scope注释使某个inject对象的生命周期限定为其Component的生命周期,即使从Component中拿到的该Inject对象为同一个。

比如,如果你想要所有的人类都放到同一个悲惨世界里面要怎么做呢?

我们从DaggerMasterGameComponent的代码可以看到,每次获取LesMiserable时都会new一个LesMiserable:

  public LesMiserable getLesMiserable() {
    return new LesMiserable();}

要改变这种获取方法

  1. 定义一个Scope注释:
    @Scope
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MasterGameScope {}
  1. 在Component类前加上这个注释:
@Component(modules = {MasterGameModule.class, SoulModule.class})
@MasterGameAnnotations.MasterGameScope
interface MasterGameComponent {
  1. 在LesMiserable类前加上这个注释:
@MasterGameAnnotations.MasterGameScope
public class LesMiserable implements Universe{

于是,Master在游戏过程中使用同一个Component对象创建LesMiserable时,都会获得同一个LesMiserable对象(当然使用不同的Component对象还是会获取不同的LesMiserable):

    public void getLesMiserable() {
        MasterGameComponent masterGameComponent = DaggerMasterGameComponent.create();

        Universe universe1 = masterGameComponent.getLesMiserable();
        Universe universe2 = masterGameComponent.getLesMiserable();
        Log.d("Master", universe1 == universe2 ? "universe1 == universe2" : "universe1 != universe2");

        Universe universe3 = masterGameComponent.getHappyPlanet();
        Universe universe4 = masterGameComponent.getHappyPlanet();
        Log.d("Master", universe3 == universe4 ? "universe3 == universe4" : "universe3 != universe4");
    }

由于HappyPlanet没有设置@Scope注释,所以他拿到的还是不同的HappPlanet,Log输出如下:

xxx D/Master: universe1 == universe2
xxx D/Master: universe3 != universe4

查看DaggerMasterGameComponent类可以看到获取LesMiserable的方法不再是每次new一个新对象了,而是获取同一个初始化就创建的对象:

  private Provider<LesMiserable> lesMiserableProvider;

  private void initialize() {
    this.lesMiserableProvider = DoubleCheck.provider(LesMiserable_Factory.create());
  }

  @Override
  public LesMiserable getLesMiserable() {
    return lesMiserableProvider.get();}

使用dagger.Binds

使用 @Binds 告知 Dagger 接口应采用哪种实现。

比如,我们想从MasterGameComponent中直接获取Faith,可以用以下方法:

  1. 在Module中用Bind注释提供Faith的实现:
@Module
abstract class MasterGameModule {

    @Binds
    public abstract Faith bindEvilFaith(Evil faithImpl);

    @Binds
    public abstract Pursuit bindPowerPursuit(Power pursuitImpl);
}
  1. 在Component中获取Faith:
@Component(modules = {MasterGameModule.class, SoulModule.class})
@MasterGameAnnotations.MasterGameScope
interface MasterGameComponent {
    Faith getFaith();
    Pursuit getPursuit();
    // 因为在Module中bind了Faith和Pursuit的实现,所以Component也可以自动构建Soul对象
    Soul getSoul();
}

Master的获取Soul的方法可以变成:

Soul soul = masterGameComponent.getSoul();

Dagger graph

上面使用Dagger后的Master game关系图如下:


Master game Dagger.png

Dagger创建的依赖图为:


Dagger graph.png

这个图没画完全,就是Dagger自动创建了所有inject类的依赖关系。


使用dagger.SubComponent

SubComponent用来把parent component分成多个独立的部分,每个部分可以定义它单独的Scope,因此可以拥有单独的生命周期。SubComponent可以用它parent component和ancestor component提供的对象,但是parent component不能用SubComponent的Modules,SubComponent也不能用它兄弟姐妹提供的对象。

例如要把上面Master game中的Soul单独构造一个SubComponent,可以用以下方法:

  1. 构造SubComponent并指定其要使用的moduels:
@Subcomponent(modules = SoulModule.class)
interface SoulComponent {

    // create Pursuit
    Money getMoney();
    Power getPower();

    // create Faith
    Evil getEvil();
    Merciful getMerciful();

    Faith getFaith();
    Pursuit getPursuit();
    // 因为在Module中bind了Faith和Pursuit的实现,所以Component也可以自动构建Soul对象
    Soul getSoul();

    SoulFactory getSoulFactory();

    @Subcomponent.Builder
    interface Builder{
        SoulComponent build();
    }
}
  1. 把这个Component加到它parent component所要用的modules中:
@Module(subcomponents = SoulComponent.class)
abstract class MasterGameModule {

这样使用parent component可以访问SubComponent中的Builder。

  1. 在parent component中添加获取SoulComponent.Builder的方法:
@Component(modules = MasterGameModule.class)
@MasterGameAnnotations.MasterGameScope
interface MasterGameComponent {

...
    SoulComponent.Builder getSoulComponent();
}
  1. build project得到DaggerMasterGameComponent类,我们可以看到在DaggerMasterGameComponent中有一个子类SoulComponent.Builder,并有获取它的方法:
final class DaggerMasterGameComponent implements MasterGameComponent {
...
  @Override
  public SoulComponent.Builder getSoulComponent() {
    return new SoulComponentBuilder();}

  static final class Builder {
    private Builder() {
    }

    public MasterGameComponent build() {
      return new DaggerMasterGameComponent();
    }
  }

  private final class SoulComponentBuilder implements SoulComponent.Builder {
    @Override
    public SoulComponent build() {
      return new SoulComponentImpl();
    }
  }

  private final class SoulComponentImpl implements SoulComponent {
    private SoulComponentImpl() {

    }

    @Override
    public Money getMoney() {
      return new Money();}

    @Override
    public Power getPower() {
      return new Power();}

    @Override
    public Evil getEvil() {
      return new Evil();}

    @Override
    public Merciful getMerciful() {
      return new Merciful();}

    @Override
    public Faith getFaith() {
      return new Evil();}

    @Override
    public Pursuit getPursuit() {
      return new Power();}

    @Override
    public Soul getSoul() {
      return new Soul(new Power(), new Evil());}

    @Override
    public SoulFactory getSoulFactory() {
      return new SoulFactory(SoulModule_ProvidePursueMoneyFactory.providePursueMoney(), SoulModule_ProvidePursuePowerFactory.providePursuePower(), SoulModule_ProvideEvilFactory.provideEvil(), SoulModule_ProvideMercifulFactory.provideMerciful());}
  }
}

最后,Master创造Soul的方法为:

MasterGameComponent masterGameComponent = DaggerMasterGameComponent.create();
Soul soul = masterGameComponent.getSoulComponent().build().getSoul();

Dagger为它创建的依赖图为:


Subcomponent Dagger graph.png

其中parent component的Dagger graph是其sub component的Dagger grapha的子图。


总结

使用 Dagger 的优势:

Dagger通过以下方式减少了代码中大量重复又容易出错的模版代码:

  1. Dagger自动生成了手写依赖注入需要构造的依赖关系图,通过这个依赖关系图,Dagger可以找到提供类实例的方式。
  2. 只要声明了某个类的依赖关系并使用注释指定如何满足它们,Dagger就会在构建时自动完成这些类的构建。
  3. 对于依赖关系图中的每个类,Dagger都会生成一个工厂类型的类(XXX_Factory.java),供内部使用该类来获取该类型的实例,并通过它们构建依赖关系。
  4. 通过@Scope注释来决定某个依赖类在其Component生命周期内是否单例。
  5. 用@SubCompnent注释把流程划分为不同的子流程,当某个子流程不再使用时可以释放它在内存中的对象,这样可以提高app的性能。
  6. build project时,Dagger会
    A. 遍历代码来构建和验证依赖关系图,来确保
    a). 每个对象的依赖关系都可以被满足,不会出现runtime exceptions。
    b). 依赖关系不存在死锁,因此不会有死循环的依赖关系图。
    B. 生成程序运行时需要用到的用来创建实例及其依赖项的class(DaggerXXXComponent、XXX_Factory、XXXModule_ProvideXXXFactory)。

缺点:

  1. 学习成本大,本来很简单的手动依赖注入方式需要通过一堆注释来完成,需要理解各个注释的原理和用法。
  2. Dagger框架本身也在不停的更新,也不时有更优秀的框架出现,如现在google官方建议可以用Hilt来替代Dagger。当应用需要从Dagger转成Hilt又是新工作量。

Android Q的Dialer代码中大规模的使用了Dagger,之后会专门介绍Dagger在Dialer模块中的应用。


参考

javax.inject:https://docs.oracle.com/javaee/7/api/javax/inject/package-summary.html

Android官网介绍依赖注入:https://developer.android.com/training/dependency-injection

dagger document:https://dagger.dev/dev-guide/

dagger github:https://github.com/google/dagger

Android官网介绍Dagger:https://developer.android.com/training/dependency-injection/dagger-basics

Android hilt:https://developer.android.com/training/dependency-injection/hilt-android

原创文章,欢迎转载,但请注明出处。

上一篇下一篇

猜你喜欢

热点阅读