Android Architecture ComponentsAndroid开发经验谈Android开发

翻译Dagger 2使用Subcomponent

2017-10-14  本文已影响141人  lanceJin

官方文档链接:https://google.github.io/dagger/subcomponents.html

1.前言


Subcomponent也是Component,只不过继承并扩展了父Component的依赖图。通过它可以将应用的依赖图分割成几个子图,不仅将应用的不同部分封装起来,还在一个Component中使用多个作用域。

除了自己Module提供的对象,Subcomponent中的对象还可以依赖它父(或祖先)Component中提供的对象。相反,父Component中的对象不能依赖Subcomponent提供的对象;Subcomponent中的对象也不可以依赖兄弟Subcomponent提供的对象。换句话说,父Component的依赖图是Subcomponent依赖图的一部分。

2.声明Subcomponent


与Component一样创建,写一个抽象类或接口,声明返回应用所需类型的抽象方法。这里使用@Subcomponent注解Subcomponent,并且添加相关Module。与Component.Builder相似,通过@Subcomponent.Builder指定一个接口为构造Subcomponent提供必要的Module。

@Subcomponent(modules = RequestModule.class)
interface RequestComponent {
  RequestHandler requestHandler();

  @Subcomponent.Builder
  interface Builder {
    Builder requestModule(RequestModule module);
    RequestComponent build();
  }
}

3.给Component添加Subcomponent


@Module注解的subcomponents属性添加Subcomponent类,并将此Module添加到父Component中。然后,Subcomponent.Builder能在父Component中被调用。

@Module(subcomponents = RequestComponent.class)
class ServerModule {}

@Singleton
@Component(modules = ServerModule.class)
interface ServerComponent {
  RequestRouter requestRouter();
}

@Singleton
class RequestRouter {
  @Inject RequestRouter(
      Provider<RequestComponent.Builder> requestComponentProvider) {}

  void dataReceived(Data data) {
    RequestComponent requestComponent =
        requestComponentProvider.get()
            .data(data)
            .build();
    requestComponent.requestHandler()
        .writeResponse(200, "hello, world");
  }
}

4.Subcomponent和作用域


将Component划分为多个Subcomponent的其中一个原因是使用作用域。通常情况下,未绑定作用域的注入类型,每次使用可能会获取到新的、独立的对象。但如果绑定了作用域,则作用范围内对它的所有使用将获取到该类型的同一个实例。标准的作用域是@Singleton,调用它注解的类型都将获得同一实例。

在Dagger中,通过作用域注解将Component与作用域关联起来。这样的话,Component持有的所有作用域对象的引用可以被复用。Module中@Provides注解的方法添加了作用域注解,那么此Module只能添加到拥有相同作用域的Component。(被@Inject注解构造方法的类型也能拥有作用域注解,这些隐式绑定能被具有相同作用域的Component及其后代Component使用,自动绑定正确的作用域。)

Subcomponent不能与任一个祖先Component拥有相同作用域,但是两个不关联的Subcomponent可以拥有相同作用域,因为对于哪里存放拥有作用域的对象不存在歧义。(两个Subcomponent明显拥有不同的作用域,即使它们使用相同的作用域注解。)

举个例子,下面的Component树中,BadChildComponent与它的父Component(RootComponent)拥有相同@RootScope注解是错误的。但是SiblingComponentOne和SiblingComponentTwo能一起使用@ChildScope注解,因为此时分别绑定相同类型的作用域并不难理解。

@RootScope @Component
interface RootComponent {
  BadChildComponent.Builder badChildComponent(); // ERROR!
  SiblingComponentOne.Builder siblingComponentOne();
  SiblingComponentTwo.Builder siblingComponentTwo();
}

@RootScope @Subcomponent
interface BadChildComponent {...}

@ChildScope @Subcomponent
interface SiblingComponentOne {...}

@ChildScope @Subcomponent
interface SiblingComponentTwo {...}

因为Subcomponent从它父Component内部创建,所以它的作用域范围必须小于父Component的。注意,这里指的是作用域范围而不是依赖图的范围。实际上,大部分直接在根Component上使用@Singleton注解。

下面的例子中,RootComponent的作用域为@Singleton@SessionScope嵌套在它之中,@RequestScope嵌套在@SessionScope之中。注意,FooRequestComponent与BarRequestComponent都被注解为@RequestScope,这没问题,因为它们是兄弟关系而不是父子关系。

@Singleton @Component
interface RootComponent {
  SessionComponent.Builder sessionComponent();
}

@SessionScope @Subcomponent
interface SessionComponent {
  FooRequestComponent.Builder fooRequestComponent();
  BarRequestComponent.Builder barRequestComponent();
}

@RequestScope @Subcomponent
interface FooRequestComponent {...}

@RequestScope @Subcomponent
interface BarRequestComponent {...}

5.Subcomponent的封装


另一个使用Subcomponent的原因是封装应用的不同部分。比如,服务器上有两个服务(或应用中有两个界面)共享一些数据,用来认证和授权,但是彼此都有一些数据专属于自己。好的做法是,为每个服务(界面)创建独立的Subcomponent,将共享的数据放在父Component中。

下面的例子中,Database对象在@Singleton注解的Component中被提供,但它所有的实现细节被封装在DatabaseComponent中。不用担心DatabaseConnectionPool对象仅存在于Subcomponent中,所有界面只通过Database对象来安排它们自己的查询,而不是直接访问DatabaseConnectionPool。

@Singleton
@Component(modules = DatabaseModule.class)
interface ApplicationComponent {
  Database database();
}

@Module(subcomponents = DatabaseComponent.class)
class DatabaseModule {
  @Provides
  @Singleton 
  Database provideDatabase(
      @NumberOfCores int numberOfCores,
      DatabaseComponent.Builder databaseComponentBuilder) {
    return databaseComponentBuilder
        .databaseImplModule(new DatabaseImplModule(numberOfCores / 2))
        .build()
        .database();
  }
}

@Module
class DatabaseImplModule {
  DatabaseImplModule(int concurrencyLevel) {}
  @Provides DatabaseConnectionPool provideDatabaseConnectionPool() {}
  @Provides DatabaseSchema provideDatabaseSchema() {}
}

@Subcomponent(modules = DatabaseImplModule.class)
interface DatabaseComponent {
  @PrivateToDatabase Database database();
}

6.抽象工厂方法定义Subcomponent


除了@Module.subcomponents,还可以在父Component中声明一个返回Subcomponent的抽象工厂方法,来将两者进行关联。如果Subcomponent需要的Module没有无参公开的构造方法,且Module没有加入父Component中,那么工厂方法必须含有Module类型的参数。对于加入了Subcomponent但没有加入父Component的Module,工厂方法也需要提供参数。(Subcomponent将自动共享需在它和它父Component之间共享的Module实例。)另外,父Component的抽象方法可能返回@Subcomponent.Builder,又或者没有Module需要被列为参数。

建议使用@Module.subcomponents,因为它允许Dagger检查Subcomponent是否被需要。通过父Component的方法添加Subcomponent表示明确需要它,即使那个方法从没被调用过。Dagger不会检查,因此肯定会生成Subcomponent,即使从来没有使用过它。

7.扩展多元绑定


就像其它依赖关系,Subcomponent可以访问父Component中的多元绑定的,而且还能给那些Map和Set添加元素。这些后加的元素仅对绑定的Subcomponent及其后代可见,不对父Component可见。

@Component(modules = ParentModule.class)
interface Parent {
  Map<String, Integer> map();
  Set<String> set();

  Child child();
}

@Module
class ParentModule {
  @Provides @IntoMap
  @StringKey("one") static int one() {
    return 1;
  }

  @Provides @IntoMap
  @StringKey("two") static int two() {
    return 2;
  }

  @Provides @IntoSet
  static String a() {
    return "a"
  }

  @Provides @IntoSet
  static String b() {
    return "b"
  }
}

@Subcomponent(modules = ChildModule.class)
interface Child {
  Map<String, Integer> map();
  Set<String> set();
}

@Module
class ChildModule {
  @Provides @IntoMap
  @StringKey("three") static int three() {
    return 3;
  }

  @Provides @IntoMap
  @StringKey("four") static int four() {
    return 4;
  }

  @Provides @IntoSet
  static String c() {
    return "c"
  }

  @Provides @IntoSet
  static String d() {
    return "d"
  }
}

Parent parent = DaggerParent.create();
Child child = parent.child();
assertThat(parent.map().keySet()).containsExactly("one", "two");
assertThat(child.map().keySet()).containsExactly("one", "two", "three", "four");
assertThat(parent.set()).containsExactly("a", "b");
assertThat(child.set()).containsExactly("a", "b", "c", "d");

8.重复的模块


当相同的模块类型被添加到Component和它任意Subcomponent中,这些Component将自动使用同一个Module实例。意味以下两种都是错误的,使用重复的Module调用Subcomponent的builder方法,或Subcomponent的工厂方法定义重复的Module作为参数。(前者在编译时不能被检查,易导致运行时错误。)

@Component(modules = {RepeatedModule.class, ...})
interface ComponentOne {
  ComponentTwo componentTwo(RepeatedModule repeatedModule); // COMPILE ERROR!
  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();

9.总结


Component之间其实可以通过@Component.dependencies属性添加依赖,这种方式与Subcomponent各有优缺点及使用场景。由于不是本篇文章的内容,所以不在此处进行比较,感兴趣的朋友可以参考这篇文章,写的很清楚。

上一篇下一篇

猜你喜欢

热点阅读