Dagger 2 - @Singleton 注释的目的是什么 class

Dagger 2 - what is the purpose of a @Singleton annotation class

来自匕首 2 Documentation 我注意到您可以 @Singleton 注释 class。将 class 标记为 @Singleton 的目的是什么,因为我已尝试在我的代码中执行此操作,但未生成单例对象。我不清楚用这个注释标记我的 class 有什么用。

文档中请重点关注以下语句:

The @Singleton annotation on an injectable class also serves as documentation. It reminds potential maintainers that this class may be shared by multiple threads.*

@Singleton
class CoffeeMaker {
    // ...
}

更新:在查看 froger_mcs 答案后,我发现在 Dagger 2 中,您可以通过模块或构造函数注入提供注入。所以下面的class,虽然不在一个模块中,但是可以注入:

@Singleton
public class MyClass {
    @Inject
    public MyClass() {

    }
}

在这个版本中,构造函数是为我们注入的,在 Android activity 中,您只需执行以下操作,它就会被提供:

@Inject
MyClass myClass;
//then in onCreate actually inject(this) from your graph of course.

@Singleton(以及任何其他范围注释)使您的 class 成为依赖关系图中的单个实例(这意味着只要 Component 对象存在,该实例就会 "singleton" ).

简而言之 - 每次您注入 @Singleton 注释 class(使用 @Inject 注释)时,只要您从同一个组件注入它,它就会是同一个实例。

有关 @Singleton 和其他作用域注释在 Dagger 2 中的工作原理的更多信息,请参阅我的博客 post:http://frogermcs.github.io/dependency-injection-with-dagger-2-custom-scopes/

@Singleton并没有真正创建一个Singleton,它只是一个Scope,建议不要使用@Singleton因为它具有误导性,它给人的印象是我们是事实上得到一个单身人士,但我们不是。

假设您使用 @Singleton 注释您的数据库依赖项,使用 Component 注释 link,现在假设您在 Activities 中初始化此 Component AB,您将在两个 Activities 中拥有不同的数据库实例,这是大多数人不想要的。

你是如何克服这个问题的?

Application class 中初始化 Component 一次,然后在 ActivitiesFragments 等其他地方静态访问它,现在这很快就会得到如果您有超过 20 个 Component's,因为您无法在您的 Application class 中初始化所有这些,否则会失控,这样做也会减慢您的应用程序启动时间。

根据我的说法,最好的解决方案是创建一个真实的 Singleton,双重检查或其他变体并将其静态用作 getInstance() 并在您的 @Provides 下使用它模块。

我知道这也让我心碎,但请理解 @Singleton 并不是真正的 Singleton,而是 Scope

@Singleton是继承@Scope的,所以在文档中说

Identifies scope annotations. A scope annotation applies to a class * containing an injectable constructor and governs how the injector reuses * instances of the type. By default, if no scope annotation is present, the * injector creates an instance (by injecting the type's constructor), uses * the instance for one injection, and then forgets it. If a scope annotation * is present, the injector may retain the instance for possible reuse in a * later injection. If multiple threads can access a scoped instance, its * implementation should be thread safe. The implementation of the scope * itself is left up to the injector. <p>In the following example, the scope annotation {@code @Singleton} ensures * that we only have one Log instance: * * <pre> * &#064;Singleton * class Log { * void log(String message) { ... } * }</pre>

你明白了吧?无论您使用什么注解或创建自定义注解,它们都是从 @Scope 继承的,它将确保作为单例。

你可以手动创建一个注解,这将有助于创建一个单例对象。

@Scope
@Retention(RetentionPolicy.CLASS)
public @interface MyApplicationScope {
}

@MyApplicationScope 注释与 @Provides 注释一起添加时,它使匕首仅创建一次对象并在将来使用相同的对象。 请记住 将此注释添加到组件接口,否则在编译期间您将遇到与范围相关的错误。

如果您正在使用 @Singleton 注释,那么每次使用 .build().

创建组件时,您可能最终都会创建新对象

什么是单例?

android

中的单例模式

A single instance of a class providing a global point of access to itself during the entire application's lifetime.

@Dagger 中的单例注解

A single instance of a class which is unique to a specific component, its access is limited to the scope of the component.

单例的用途

在依赖图(组件)中提供 class 的单个实例。一个组件通常在应用程序级别初始化,因为它在整个应用程序生命周期中只执行一个组件,并且可以被所有活动和片段访问。

举个例子:

CoffeeComponent.kt

@Singleton
@Component
interface CoffeeComponent {

  fun getCoffeeMaker():CoffeeMaker
  fun inject(activityA: ActivityA)
  fun inject(activityB: ActivityB)
}

CoffeeMaker.kt

@Singleton
class CoffeeMaker @Inject constructor()

CoffeeAplication.kt

class CoffeeApplication : Application() {

  private val component by lazy {
    DaggerCoffeeComponent.builder().build()
  }

  fun getAppComponent(): CoffeeComponent = component
}

推荐练习

Always go for lazy initialization of your component.
Scenario: say, you are team decided to add an onboarding/tutorial screen or incorporate some other design which doesn't require the component during the initial screens, it will help minimize the startup delay. Always remember, component initialization is expensive.

ActivityA.kt

import dagger.Lazy

class ActivityA: AppCompatActivity() {

  @Inject
  lateinit var coffeeMaker1:Lazy<CoffeeMaker>
  @Inject
  lateinit var coffeeMaker2:Lazy<CoffeeMaker>
  private val component by lazy {
    (application as CoffeeApplication).getAppComponent()
}
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    btn_activityB.setOnClickListener { startActivity(Intent(this, NewActivity::class.java)) }

    component.inject(this)

    println("Activity A CoffeeMaker 1 - ${coffeeMaker1.get()}")
    println("Activity A CoffeeMaker 2 - ${coffeeMaker2.get()}")
  }
}


如果你的 class 构造起来很昂贵,请使用 dagger 的 Lazy 初始化,请不要将它与 kotlin 的 Lazy 混淆。你必须导入

import dagger.Lazy

@Inject
lateinit var coffeeMaker1:Lazy<CoffeeMaker>

ActivityB.kt

class ActivityB: AppCompatActivity() {

@Inject
lateinit var coffeeMaker1:Lazy<CoffeeMaker>
@Inject
lateinit var coffeeMaker2:Lazy<CoffeeMaker>
private val component by lazy { 
(application as CoffeeApplication).getAppComponent() }

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_new)
    component.inject(this)

    println("Activity B CoffeeMaker 1 - ${coffeeMaker1.get()}")
    println("Activity B CoffeeMaker 2 - ${coffeeMaker2.get()}")
}
}

您将得到日志输出

:

If you want to share a singleton instance between activities, lazily initialize them in the application level, if you initialize them in an activity you will end up with different instance as the components are different