Dagger 2为每个Fragment(或Activity等)自定义作用域

39

我查看了几篇不同的文章,它们似乎提出了Dagger 2中实现自定义作用域的两种不同方法:

  1. MVP Presenters that Survive Configuration Changes Part-2 (Github repo):

    • 为每个Fragment使用唯一的自定义作用域,例如对于Hello1Fragment和Hello2Fragment分别使用@Hello1Scope@Hello2Scope
  2. Tasting Dagger 2 on Android:

    • 为所有Fragment使用单个自定义作用域,例如@PerFragment

据我所知,就像方法2中一样,定义一个可用于所有片段的单个作用域应该是可以的 (即,@PerFragment)。事实上 (如果我理解有误,请纠正我),似乎自定义作用域的名称并不重要,只有在哪里创建子组件(即在Application、Activity或Fragment中)才重要。

在像方法1中为每个片段定义唯一作用域的情况下,是否有任何用例呢?

2个回答

95

在阅读了 @vaughandroid 的答案以及 Dagger 2 中组件(对象图)的生命周期是由什么决定的?之后,我认为我已经足够理解自定义作用域来回答我的问题。

首先,在处理 dagger2 中的组件、模块和作用域注释时,请遵循以下几个规则:

  • 组件 必须 有一个(单一的)作用域注释(例如 @Singleton@CustomScope)。
  • 模块 没有 作用域注释。
  • 模块方法 可以有 与其 组件 匹配或不带作用域的(单一的)作用域,其中:
    • Scoped:表示为每个组件实例创建单个实例。
    • Unscoped:表示每次 inject() 或 provider 调用时都会创建一个新实例。
    • 注意:Dagger2 仅将 @Singleton 保留为根组件(及其模块)使用。子组件必须使用自定义作用域,但该作用域的功能与 @Singleton 完全相同。

现在,来回答问题:我会为每个概念上不同的作用域创建一个新的命名作用域。例如,创建一个 @PerActivity@PerFragment@PerView 注释,指示应在何处实例化组件,从而指示其生命周期。

注意:这是两种极端之间的妥协。考虑根组件和 n 个子组件的情况,您将需要:

  • 至少 2 个注释(@Singleton@SubSingleton)以及
  • 最多 n+1 个注释(@Singleton@SubSingleton1,...,@SubSingletonN)。

示例:

应用程序:

/** AppComponent.java **/ 
@Singleton
@Component( modules = AppModule.class )
public interface AppComponent{
    void inject(MainActivity mainActivity);
}

/** AppModule.java **/
@Module
public class AppModule{
    private App app;

    public AppModule(App app){
        this.app = app;
    }

    // For singleton objects, annotate with same scope as component, i.e. @Singleton
    @Provides @Singleton public App provideApp() { return app; }
    @Provides @Singleton public EventBus provideBus() { return EventBus.getDefault(); }
}

片段(Fragment):

/** Fragment1Component.java **/
@PerFragment
@Component( modules = {Fragment1Module.class}, dependencies = {AppComponent.class} )
public interface Fragment1Component {
    void inject(Fragment1 fragment1);
}

/** Fragment1Module.java **/ 
@Module
public class Fragment1Module {
    // For singleton objects, annotate with same scope as component, i.e. @PerFragment
    @Provides @PerFragment public Fragment1Presenter providePresenter(){
        return new Fragment1Presenter();
    }
}

/** PerFragment.java **/ 
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface PerFragment {}

你能提供这些规则的详细信息吗? - peacepassion
@peacepassion,不确定你需要什么样的详细信息,但我已经更新了答案,并提供了我完成的一个项目的示例。 - bcorso
你会在哪里实例化 Fragment1Component,并在哪里使用它?onCreateView中吗? - mbmc
@tibo 我建议在 onCreate 中实例化它。 - Giulio Piancastelli
基于关于作用域和配置更改以及自定义作用域的类似阅读,我最终组合了这个小,以帮助每个片段/活动、演示文稿绑定等的作用域/组件创建。也许这是你会发现有用的东西。 - laenger
如果我在我的子组件中用@Singleton或者@ElectionScope2k16注释方法,与使用@ActivityScope注释没有区别吗?无论如何(除非未标记范围,这会导致每次注入时创建),它只会绑定到子组件的生命周期? - Zackline

22

你的理解是正确的。命名范围允许您传达意图,但它们的工作方式都相同。

  • 对于范围限定的提供程序方法,每个组件实例将创建一个提供的对象实例。
  • 对于非范围限定的提供程序方法,每个组件实例在需要注入对象时都会创建一个新的提供的对象实例。

组件实例的生命周期非常重要。即使是有范围限定的对象,在两个不同的组件实例中也会提供不同的对象实例。

范围名称应指示所提供对象的生命周期(与组件实例的生命周期相匹配),因此@PerFragment 对我来说更有意义。

从快速浏览“MVP Presenters...” 教程中,我不确定作者分别使用不同范围的意图是什么。因为名称只是可丢弃的名称,所以不要过多解读。


我猜他计划将“逻辑封装到自定义视图中”,因此每个视图都有自己的Presenter,每个Presenter都有自己的作用域。 - EpicPandaForce
@EpicPandaForce 是的,你说得对,在方法1中,意图是将每个“MVP-View”与一个“Android-Custom-View”关联起来,其中每个视图都有自己的Presenter(尽管通常我看到MVP-View与Fragments/Activities相关联)。然而,相同的论点似乎适用——即使用@PerView而不是每个视图都有自己的范围,对吧? - bcorso
也许我只是一个平庸的程序员,但尽管我喜欢DI、MVP和所有干净代码的东西,这个“MVP Presenters…”系列对我来说感觉过度设计了。我为那些最终不得不维护像public abstract class PresenterControllerFragment<C extends HasPresenter<P>, P extends Presenter> extends ComponentControllerFragment<C>ComponentCacheDelegate等代码库而感到遗憾。 - Konrad Morawski

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