Dagger @Reusable scope vs @Singleton

2019-01-13 15:59发布

问题:

From the User's Guide:

Sometimes you want to limit the number of times an @Inject-constructed class is instantiated or a @Provides method is called, but you don’t need to guarantee that the exact same instance is used during the lifetime of any particular component or subcomponent.

Why would I use that instead of @Singleton?

回答1:

Use @Singleton if you rely on singleton behavior and guarantees. Use @Reusable if an object would only be a @Singleton for performance reasons.


@Reusable bindings have much more in common with unscoped bindings than @Singleton bindings: You're telling Dagger that you'd be fine creating a brand-new object, but if there's a convenient object already created then Dagger may use that one. In contrast, @Singleton objects guarantee that you will always receive the same instance, which can be much more expensive to enforce.

In general, Dagger and DI prefer unscoped objects: Creating a new object is a great way to keep state tightly-contained, and allows for objects to be garbage-collected as soon as the dependent object can. Dagger shows some of this preference built-in: In Dagger unscoped objects can be mixed in to any component or module, regardless of whether the component is scope-annotated. This type of unscoped binding is also useful for stateless objects like injectable (mockable) utility classes and implementations of strategy, command, and other polymorphic behavioral design patterns: The objects should be bound globally and injected for testing/overrides, but instances don't keep any state and short-lived or disposable.

However, in Android and other performance- and memory-constrained environments, it goes against performance recommendations to create a lot of temporary objects, because instance creation and garbage collection are both more-expensive processes than on desktop VMs. This leads to the pragmatic solution of marking an object @Singleton, not because it's important to always get the same instance, but just to conserve instances. This works, but is semantically-weak, and also has memory and speed implications: Your short-lived util or strategy pattern object now has to exist as long as your application exists, and must be accessed through double-checked locking, or else you risk violating the "one instance only" @Singleton guarantee (that is unnecessary here). This can be a source of increased memory usage and synchronization overhead.

The compromise is in @Reusable bindings, which have instance-conserving properties like @Singleton but are excepted from the scope-matching @Component rule just like unscoped bindings—which gives you more flexibility about where you install them. (See tests.) They have a lifespan only as long as the outermost component that uses them directly, and will opportunistically use an instance from an ancestor to conserve further, but without double-checked locking to save on creation costs. Finally, and most importantly, they're a signal to you and future developers about the way the class is intended to be used.

In short, @Singleton would work, but @Reusable has some distinct performance advantages if the whole point is performance instead of object lifecycle.


Follow-up question from saiedmomen: "Just to be 100% clear things like okhttpclient, retrofit and gson should be declared @Reusable. right??"

Yes, in general I think it'd be good to declare stateless utilities and libraries as @Reusable. However, if they secretly keep some state—like handling connection limits or batching across all consumers—you might want to make them @Singleton, and if they are used very infrequently from a long-lived component it might still make sense to make them scopeless so they can be garbage-collected. It's really hard to make a general statement here that works for all cases and libraries: You'll have to decide based on library functionality, memory weight, instantiation cost, and expected lifespan of the objects involved.