Dagger 2:向父组件添加子组件

5

大家好,我在理解Dagger 2新的添加子组件方法(在Dagger 2.7中添加)方面遇到了问题。请看下面的示例:

@Component(modules = {AppModule.class, MainActivityBinder.class})
@Singleton
interface AppComponent
{
   inject(MyApplication _)
}

@Subcomponent(modules = ActivityModule.class)
interface ActivitySubcomponent
{
   inject(MainActivity _)

   @Subcomponent.Builder
   interface Builder
   {
      @BindInstance
      Builder activity(Activity activity)

      ActivitySubcomponent build();
   }
}

首先,我有一个名为AppComponent的根组件,它提供单例(如retrofit、okhttp等)给AppModule。在ActivitySubcomponent中,我提供了ActivityModule,其中包含特定于该活动的依赖项。现在,子组件必须添加到AppComponent中,因此我创建了一个指定模块MainActivityBinder,它具有注释@ Module.subcomponents并指向绑定子组件的点,但我遇到了第一个问题:该绑定模块的主体应该是什么?

@Module(subcomponents = ActivitySubcomponent.class)
public class MainActivityBinder
{
  //what body of this class should be ??
}

我知道,这个想法是我可以绑定子组件或它们的构建器。第二个问题是什么时候绑定构建器,什么时候绑定子组件?例如,我的ActivitySubcomponent需要活动上下文,所以我创建了一个构建器,在这种情况下,最好在MainActivityBinder中提供一个构建器吗?第三个问题是如何调用组件构建器以及如何获取应用程序组件的子组件?在标准子组件工厂中,我添加了一个方法到AppComponent,返回子组件,我可以定义参数(例如给出活动上下文,如下所示)

@Component(modules = {AppModule.class})
@Singleton
interface AppComponent
{
   ActivitySubcomponents newActivitySubcomponents(Activity activity);

   inject(MyApplication _);
}

// in MainActivity
appComponent.newActivitySubcomponents(this).build().inject(this);

所以在新的子组件中添加方法来实现这种行为?

1
为什么不使用dagger-android?有更简单的选择。 - Zuluft
3
@Zuluft您的建议不是主题,我的目标是理解Module.subcomponents,我读到的文档说:“使用Module.subcomponents更好,因为它允许Dagger检测是否请求了子组件。通过父组件上的方法安装子组件是对该组件的明确请求,即使该方法从未被调用。”,因此我想尝试测试新方法的工作原理,但我无法正确定义绑定模块。 - Heroes84
1个回答

1
  1. Your module MainActivityBinder is allowed to be empty, and should be if you don't have anything else to bind with it. Empty (annotation-only) modules are also useful when you only use Module.includes, such as when you want to keep a module list in one place rather than duplicating it among several components. The subcomponents attribute on the annotation is enough for Dagger to understand what you're trying to do.

  2. You can inject a FooSubcomponent or Provider if and only if it has no @BindsInstance methods or instantiable modules (that Dagger can't instantiate). If all of your modules are interfaces, abstract classes, or modules that have public zero-arg constructors, then you may inject the subcomponent directly. Otherwise you should inject your subcomponent builder instead.

  3. You can get to your subcomponent builder by creating a method that returns it on your AppComponent, just as you can for any binding that exists in the graph:

    @Component(modules = {AppModule.class, MainActivityBinder.class})
    @Singleton
    interface AppComponent {
      ActivitySubcomponent.Builder activitySubcomponentBuilder();
    
      inject(MyApplication _)
    }
    

    You may also inject it into the object of your choice.

    @Inject ActivitySubcomponent.Builder activitySubComponentBuilder;
    activitySubComponentBuilder.activity(this).build().inject(this);
    
    // You can also inject a Provider<ActivitySubcomponent.Builder> if you want,
    // which is a good idea if you are injecting this directly into your Application.
    // Your Application will outlive your Activity, and may need to inject several
    // instances of the Activity across application lifetime.
    @Inject Provider<ActivitySubcomponent.Builder> activitySubComponentBuilderProvider;
    activitySubComponentBuilderProvider.get().activity(this).build().inject(this);
    
尽管注入子组件构建器与在组件上调用方法(返回Builder或返回子组件)没有太大的优势,但注入构建器有几个优点:
  • Dagger无法判断您是否调用了组件上的方法,因此即使您的子组件未被使用,它仍会生成和编译代码。Dagger可以判断您是否尝试注入构建器,因此如果没有子组件/构建器注入和没有方法,则Dagger将跳过生成子组件的代码。
  • 如果您的代码库足够大,需要将其拆分成不同的目标进行编译,则工厂方法技术可能会在某些依赖循环中捕获到您的应用程序组件和模块依赖于所有内容,并且您只能从组件本身访问子组件。通过可注入的子组件构建器,您可以更多地选择如何访问子组件或构建器。

感谢@Jeff Bowman的深入解释。我有一个关于测试中交换子组件的问题,根据官方文档的第二个选项(https://google.github.io/dagger/testing.html)。我该如何配置`AppComponent`以设置测试子组件`MainActivitySubcomponentTest`(它继承自MainActivitySubcomponent)?我尝试在Component.Builder中进行setter操作,但没有成功。 - Heroes84
@Heroes84:不客气!不幸的是,如果你想替换子组件,没有真正好的方法,因为子组件实现是生成的组件代码的私有细节。如果你想要能够替换你的父组件,那么你可以将你的活动子组件转换成一个完整的“@Component”,并具有依赖关系——但是这需要很多单独的工作和权衡。如果你很好地构建了你的注入(直接注入子组件),你也可以手写或模拟子组件及其构建器,并在测试中使用它们。 - Jeff Bowman
此外,考虑到Proguard应该会删除未使用的部分,那么产生的不必要的代码是否是一个问题呢? - Florian Walther
@FlorianWalther 1. 注入同一个对象两次(包括使用两个不同的组件)最多是容易出错的,如果能够工作的话;Dagger 组件不喜欢注入它们无法完全注入的类。话虽如此,子组件可以访问所有父组件的绑定,因此使用 Activity 组件进行一次注入即可。2. Proguard 可能 会为您删除未使用的代码,但您仍然需要_满足所有未使用方法所需的绑定_,而且随着输入的增加,Proguard 的时间也会增加,Proguard 可能 无法有效地删除该代码。 - Jeff Bowman
@FlorianWalther 我承认这是个坏主意,而且很难做到完美,但我试图保持与原问题代码的一致性。无论如何,应用程序不太可能访问要注入的Activity,因为Android可以自由地创建/销毁/更改Activity实例,所以真正正确的模式是使用dagger.android。在内部,dagger.android为您创建一个activity子组件,在应用程序中注入一个构建器映射,通过getApplication获取构建器,构建它,并在onCreate中调用它来inject(this) - Jeff Bowman
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接