Dagger 2中的作用域

26

我可能漏掉了什么,但我认为像@Singleton这样的Scope用于定义“作用域生命周期”。

我在一个Android应用中使用Dagger 2(但我认为这个问题与Android无关)。

我有1个模块:

@Module public class MailModule {

  @Singleton @Provides public AccountManager providesAccountManager() {
    return new AccountManager();
  }

  @Singleton @Provides public MailProvider providesMailProvider(AccountManager accountManager) {
    return new MailProvider(accountManager);
  }
}

我有两个不同的组件,它们都是 @Singleton 作用域:

@Singleton
@Component(modules = MailModule.class)
public interface LoginComponent {

  public LoginPresenter presenter();
}


@Singleton
@Component(
    modules = MailModule.class
)
public interface MenuComponent {

  MenuPresenter presenter();

}

无论是 MenuPresenter 还是 LoginPresenter,它们都有一个带有@Inject的构造函数。其中,MenuPresenter期望通过参数传入 MailProvider,而 LoginPresenter则需要一个 AccountManager

  @Inject public MenuPresenter(MailProvider mailProvider) { ... }

  @Inject public LoginPresenter(AccountManager accountManager) { ... }

但是每次我使用组件来创建MenuPresenterLoginPresenter时,我都会得到一个全新的MailProviderAccountManager实例。我认为它们应该在同一作用域内,并且应该是单例模式(在相同作用域内)。

我是否完全理解错误?如何在Dagger 2中定义多个组件的真正单例?

2个回答

53

我假设LoginComponentMenuComponent是分别在不同的地方使用的,例如在LoginActivityMenuActivity中。每个组件都是在Activity.onCreate中构建的。如果是这样的话,那么每次创建新的活动时,组件、模块和依赖项也会被重新创建,无论它们绑定到什么作用域。因此,您每次获得MainProviderAccountManager的新实例。

MenuActivityLoginActivity具有单独的生命周期,因此来自MailModule的依赖项不能在两者中都是单例的。您需要声明根组件,并使用@Singleton作用域进行注释(例如,在Application子类中),使MenuComponentLoginComponent依赖于它。活动级别的组件不能是@Singleton作用域,最好使用@Scope注释创建自己的作用域,例如:

@Retention(RetentionPolicy.RUNTIME)
@Scope
public @interface MenuScope {
}

或者您可以将它们保持未作用域限制。

关于这里的所有作用域,以下是来自最初的Dagger 2提案的简要介绍:

@Singleton
@Component(modules = {…})
public interface ApplicationComponent {}
该声明使dagger能够强制执行以下约束条件:
  • 给定组件只能具有未作用域化的绑定(包括类上的作用域注释)或已声明作用域的绑定。 即一个组件不能表示两个作用域。当未列出作用域时,绑定只能是未作用域的。
  • 作用域组件只能有一个作用域依赖项。这是强制执行两个组件不会各自声明其自己的作用域绑定的机制。例如,两个单例组件都有自己的@Singleton Cache将被破坏。
  • 组件的作用域不得出现在其传递依赖项中。例如:SessionScoped -> RequestScoped - > SessionScoped没有任何意义,是一个错误。
  • @Singleton在处理中是特殊的,因为它不能有任何作用域依赖关系。每个人都希望Singleton是“根”。

这些规则的目标是在应用作用域时,结构与我们使用Dagger 1.0加上ObjectGraphs添加的相同结构进行组合,但具有所有绑定及其范围的静态知识。换句话说,在应用作用域时,这限制了可以构建的图形,仅限于可正确构造的图形。

根据我的经验,完全不使用@Singleton更加清晰。相反,我使用@ApplicationScope。它用于在整个应用程序上定义单例,没有像@Singleton一样的附加限制。

希望这可以帮助你 :)。这很棘手,需要时间才能快速理解,至少对我来说是这样。


但是一个组件只能有一个Scope注释,对吧?如果我有一个带有@Application和LoginComponent的@Activity范围的应用程序组件,我该怎么办? - sockeqwe
2
正确。组件不能用两个作用域进行注释。如果在注释@Component(dependencies = ApplicationComponent.class)中定义,则活动作用域组件将具有应用程序作用域组件的所有依赖项。将带有作用域的组件视为图形和子图形。应用程序组件及其作用域-根图,活动组件及其作用域-根的子图。 - Kirill Boyarshinov
避免使用@Singleton作用域是否允许将@Application依赖项注入到@Activity作用域中?例如,如果MyPresenter是@Application作用域,并且我想将其注入到@Activity作用域的MyActivity中。 - AAverin
@AAverin 是的。但我认为也可以通过使用@Singleton来实现。 - Kirill Boyarshinov
这绝对是我找到的最好的解释!!+1 - Lisa Anne
"@Singleton在特殊情况下被处理,它不能有任何作用域依赖项。每个人都期望Singleton是“根”。 Dagger 2实际上是否强制执行这一点?" - Florian Walther

7
您可以按照以下方式来定义多个组件的真正单例。我假设@ApplicationScoped@ActivityScoped是不同的作用域。
@Module public class MailModule {
  @Provides @ApplicationScoped 
  public AccountManager providesAccountManager() {
    return new AccountManager();
  }

  @Provides @ApplicationScoped
  public MailProvider providesMailProvider(AccountManager accountManager) {
        return new MailProvider(accountManager);
  }
}

那么,可以为 MailModule 定义一个 MailComponentLoginComponentMenuComponent 可以依赖于 MailComponent

@ApplicationScoped
@Component(modules = MailModule.class)
public interface MailComponent {
  MailProvider mailProvider();
  AccountManager accountManager();
}

@ActivityScoped
@Component(dependencies = MailComponent.class)
public interface LoginComponent {
  LoginPresenter presenter();
}

@ActivityScoped
@Component(dependencies = MailComponent.class)
public interface MenuComponent {
  MenuPresenter presenter();
}
< p > MailComponent 可以按照下面的方式初始化,并且可以在下面示例的 MenuComponentLoginComponent 中使用。

MailComponent mailComponent = DaggerMailComponent.builder().build();

DaggerMenuComponent.builder().mailComponent(mailComponent).build();

DaggerLoginComponent.builder().mailComponent(mailComponent).build()            

1
在每个模块方法的顶部分别写@ApplicationScoped和在@Module顶部一次性写有什么区别吗? - Jemshit Iskenderov
2
@JemshitIskenderov - 在@Module的顶部放置@ApplicationScoped注释将没有任何效果。模块的目的是通过提供程序方法(使用@Provides注释注释的方法)提供依赖项。这些提供程序方法可能具有范围,也可能没有范围。因此,在提供程序方法级别定义范围变得很重要。 - Praveer Gupta

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