阅读 339

Dagger2利器系列二:懒/重加载+Component 的组织关系

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

一:懒/重加载

1.1 Dagger2 中的懒加载

智能懒加载,是Dagger2实现高性能的重要举措之一:在需要的时候才对成员变量进行初始化,可以大幅缩短应用初始化的时间。

使用方法:用Lazy修饰变量即可。Lazy 是泛型类,接受任何类型的参数。

@Inject
Lazy<Object> object;
复制代码

《Dagger2利器系列一:入门到使用》3.2小节中的demo为例,只要用Lazy修饰需要被注入的对象即可。

public class Car {
    /**

   * @Inject@Inject有两个作用,一是用来标记需要依赖的变量,以此告诉Dagger2为它提供依赖
     /
         @Inject
         Lazy<Engine> engine;

    public Car() {
        DaggerCarComponent.builder().build().inject(this);
    }
     
    public Engine getEngine() {
        return this.engine;
    }
     
    public static void main(String ... args){
        Car car = new Car();
        System.out.println(car.getEngine());
    }

}
复制代码

1.2 Provider 强制重新加载

@Singleton 标注实现的单例可以让我们每次获取的都是同一个对象(暂不细究全局/局部单例),但有时,我们希望每次都创建一个新的实例,这种情况与 @Singleton 完全相反。Dagger2 通过 Provider 就可以实现。它的使用方法和 Lazy 很类似。

使用方法:用Provider修饰变量即可。Provider是泛型类,接受任何类型的参数。

@Inject
Provider<Object> object;
复制代码

还是拿《Dagger2利器系列一:入门到使用》3.2小节中的demo为例,只要用Provider修饰需要被注入的对象即可。

public class Car {
    /**
     * @Inject@Inject有两个作用,一是用来标记需要依赖的变量,以此告诉Dagger2为它提供依赖
     */
    @Inject
    Provider<Engine> engine;

    public Car() {
        DaggerCarComponent.builder().build().inject(this);
    }
     
    public Engine getEngine() {
        return this.engine;
    }
     
    public static void main(String ... args){
        Car car = new Car();
        System.out.println(car.getEngine());
    }

}

复制代码

但是,需要注意的是 Provider 所表达的重新加载是说每次重新执行 Module 相应的 @Provides 方法,如果这个方法本身每次返回同一个对象,那么每次调用 get() 的时候,对象也会是同一个。

二:Component 的组织依赖关系

2.1 前言

Component 的组织依赖关系主要参考了Dagger 2 完全解析(三),Component 的组织关系与 SubComponent这篇文章,我觉得写的很好,例子很棒,自己写下来应该差不多,所以这一小节主要以该篇文章内容结构为主体来写,建议大家也看看。进入正文。

在实际项目中,有多个需要注入依赖的对象,也就是说会有多个 Component,它们之间会有相同的依赖,那么该如何处理它们之间的关系呢?以下面这个场景为例子:

public class Man {
    @Inject
    Car car;

    public void goWork() {
        ...
        car.go();
        ...
    }

}

public class Friend {
    @Inject
    Car car;    // 车是向 Man 借的

    public void goSightseeing() {
        ...
        car.go();
        ...
    }

}
复制代码

项目场景如下:Man 有一辆车,Friend 没有车,但是他可以借 Man 的车出去玩,但提供 Car 实例的,是CarModule不变。

这种情况下我们该怎么设计 Component 呢?很多人第一时间会这么设计:

// @ManScope 和 @FriendScope 都是自定义的作用域
@ManScope
@Component(modules = CarModule.class)
public interface ManComponent {
    ...
}

@FriendScope
@Component(modules = CarModule.class)
public interface FriendComponent {
    ...
}
复制代码

这种做法最简单,ManComponent和FriendComponent需要的car都在各自modules中提供了,也就是说:Man和Friends都有CarModule去提供car。所以这时,发现问题了嘛?这个car已经不是Man他的car了!

问题:

(1)有时依赖实例需要共享,例如上面场景中,Friend 的 car 是向 Man 借的,所以 FriendComponent应该使用ManComponent中的 car 实例。

(2)Scope 作用域容易失效,例如 CarModule 的provideCar()使用 @Singleton 作用域,FriendComponent和ManComponent也要用 Singleton 标注,但它们都会持有一个car 实例。

所以 FriendComponent 需要依赖 ManComponent 提供的 car 实例,这就是 Component 组织关系中的一种:依赖关系。

2.2 Component 的组织关系

在 Dagger 2 中 Component 的组织关系分为两种:

  • 依赖关系:一个 Component 依赖其他 Compoent ,以获得其中公开的依赖实例,用 Component 中的dependencies声明。

  • 继承关系:一个 Component 继承(扩展)其他的 Component, 以获得其他的Component中的依赖,SubComponent 就是继承关系的体现。

2.2.1 依赖关系

Friend 与 Man 场景中的依赖关系图:

具体的实现代码:

@ManScope
@Component(modules = CarModule.class)
public interface ManComponent {
    void inject(Man man);

    Car car();  //必须向外提供 car 依赖实例的接口,表明 Man 可以借 car 给别人

}

@FriendScope
@Component(dependencies = ManComponent.class)
public interface FriendComponent {
    void inject(Friend friend);
}
复制代码

注:因为 FriendComponent 和 ManComponent 是依赖关系,所以其中一个声明了作用域的话,另外一个也必须声明。而且它们的 Scope 不能相同,ManComponent 的生命周期 >= FriendComponent 的。FriendComponent 的 Scope 不能是 @Singleton,因为 Dagger 2 中 @Singleton 的 Component 不能依赖其他的 Component。

编译时生成的代码中 DaggerFriendComponent 的 Provider实现中会用到manComponent.car()来提供 car 实例,如果 ManComponent 没有向外提供 car 实例的接口的话,DaggerFriendComponent 就会注入失败。

依赖注入:

ManComponent manComponent = DaggerManComponent.builder()
    .build();

FriendComponent friendComponent = DaggerFriendComponent.builder()
    .manComponent(manComponent)
    .build();

friendComponent.inject(friend);
复制代码

依赖关系就跟生活中的朋友关系相当,注意事项如下:

被依赖的 Component 需要把暴露的依赖实例用显式的接口声明,如上面的Car car(),我们只能使用朋友愿意分享的东西。

依赖关系中的 Component 的 Scope 不能相同,因为它们的生命周期不同。

2.2.3 继承关系

继承关系跟面向对象中的继承的概念有点像,SubComponent 称为子 Component,类似于平常说的子类。下面先看看下面这个场景:

public class Man {
    @Inject
    Car car;
    ...
}

public class Son {
    @Inject
    Car car;

    @Inject
    Bike bike;

}
复制代码

Son 可以开他爸爸 Man 的车 car,也可以骑自己的自行车 bike。依赖关系图:

上图中 SonComponent 在 ManComponent 之中,SonComponent 子承父业,可以访问 parent Component 的依赖,而 ManComponent 只知道 SonComponent 是它的 child Component,可以访问 SubComponent.Builder,却无法访问 SubComponent 中的依赖。

@ManScope
@Component(modules = CarModule.class)
public interface ManComponent {
    void inject(Man man);   // 继承关系中不用显式地提供暴露依赖实例的接口
}

@SonScope
@SubComponent(modules = BikeModule.class)
public interface SonComponent {
    void inject(Son son);

    @Subcomponent.Builder
    interface Builder { // SubComponent 必须显式地声明 Subcomponent.Builder,parent Component 需要用 Builder 来创建 SubComponent
        SonComponent build();
    }

}
复制代码

@SubComponent的写法与@Component一样,只能标注接口或抽象类。与依赖关系一样,SubComponent 与 parent Component 的 Scope 不能相同,只是 SubComponent 表明它是继承扩展某 Component 的。

怎么表明一个 SubComponent 是属于哪个 parent Component 的呢?只需要在 parent Component 依赖的 Module 中的subcomponents加上 SubComponent 的 class,然后就可以在 parent Component 中请求 SubComponent.Builder。

@Module(subcomponents = SonComponent.class)
public class CarModule {
    @Provides
    @ManScope
    static Car provideCar() {
        return new Car();
    }
}

@ManScope
@Component(modules = CarModule.class)
public interface ManComponent {
    void injectMan(Man man);

    SonComponent.Builder sonComponent();    // 用来创建 Subcomponent

}
复制代码

SubComponent 编译时不会生成 DaggerXXComponent,需要通过 parent Component 的获取 SubComponent.Builder 方法获取 SubComponent 实例。

ManComponent manComponent = DaggerManComponent.builder()
    .build();

SonComponent sonComponent = manComponent.sonComponent()
    .build();
sonComponent.inject(son);
复制代码

继承关系和依赖关系最大的区别就是:继承关系中不用显式地提供依赖实例的接口,SubComponent 继承 parent Component 的所有依赖。

2.3 依赖关系 vs 继承关系

相同点:

  • 两者都能复用其他 Component 的依赖

  • 有依赖关系和继承关系的 Component 不能有相同的 Scope

区别:

  • 依赖关系中被依赖的 Component 必须显式地提供公开依赖实例的接口,而 SubComponent 默认继承 parent Component 的依赖。

  • 依赖关系会生成两个独立的 DaggerXXComponent 类,而 SubComponent 不会生成 独立的 DaggerXXComponent 类。

在 Android 开发中,Activity 是 App 运行中组件,Fragment 又是 Activity 一部分,这种组件化思想适合继承关系,所以在 Android 中一般使用 SubComponent。

2.4 SubComponent 的其他问题

2.4.1 抽象工厂方法定义继承关系

除了使用 Module 的subcomponents属性定义继承关系,还可以在 parent Component 中声明返回 SubComponent 的抽象工厂方法来定义:

@ManScope
@Component(modules = CarModule.class)
public interface ManComponent {
    void injectMan(Man man);

    SonComponent sonComponent();    // 这个抽象工厂方法表明 SonComponent 继承 ManComponent

}
复制代码

这种定义方式不能很明显地表明继承关系,一般推荐使用 Module 的subcomponents属性定义。

2.4.2 重复的 Module

当相同的 Module 注入到 parent Component 和它的 SubComponent 中时,则每个 Component 都将自动使用这个 Module 的同一实例。也就是如果在 SubComponent.Builder 中调用相同的 Module 或者在返回 SubComponent 的抽象工厂方法中以重复 Module 作为参数时,会出现错误。(前者在编译时不能检测出,是运行时错误)

@Component(modules = {RepeatedModule.class, ...})
interface ComponentOne {
  ComponentTwo componentTwo(RepeatedModule repeatedModule); // 编译时报错
  ComponentThree.Builder componentThreeBuilder();
}

@Subcomponent(modules = {RepeatedModule.class, ...})
interface ComponentTwo { ... }

@Subcomponent(modules = {RepeatedModule.class, ...})
interface ComponentThree {
  @Subcomponent.Builder
  interface Builder {
    Builder repeatedModule(RepeatedModule repeatedModule);
    ComponentThree build();
  }
}

DaggerComponentOne.create().componentThreeBuilder()
    .repeatedModule(new RepeatedModule()) // 运行时报错 UnsupportedOperationException!
    .build();
复制代码

2.5 总结

Component 之间共用相同依赖时,可以有两种组织关系:依赖关系与继承关系。在 Android 开发中,一般使用继承关系,以 AppComponent 作为 root Component,AppComponent 一般还会使用 @Singleton 作用域,而 ActivityComponent 为 SubComponent。

参考文章:

dagger2从入门到放弃-Component的继承体系、局部单例

Dagger 2 完全解析(三),Component 的组织关系与 SubComponent

文章分类
Android
文章标签