美文网首页
Why Dagger

Why Dagger

作者: 朱兰婷 | 来源:发表于2020-07-15 11:41 被阅读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

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

    相关文章

      网友评论

          本文标题:Why Dagger

          本文链接:https://www.haomeiwen.com/subject/idmacktx.html