Dagger2 自定义范围:自定义范围 (@ActivityScope) 是如何工作的?
Dagger2 Custom Scopes : How do custom-scopes (@ActivityScope) actually work?
我正在阅读具有 @PerActivity
作用域的 Dagger2 Component Scopes Test on GitHub, and I've seen a "custom scope" defined for activities called @ActivityScope
, but I've seen it in other projects including the 4-module CleanArchitecture 的源代码。
但从字面上看,@ActivityScope
注释的代码如下:
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.inject.Scope;
/**
* Created by joesteele on 2/15/15.
*/
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {
}
它 "magically" 可用于模块:
@Module
public class ActivityModule {
@Provides @ActivityScope Picasso providePicasso(ComponentTest app, OkHttpClient client) {
return new Picasso.Builder(app)
.downloader(new OkHttpDownloader(client))
.listener(new Picasso.Listener() {
@Override public void onImageLoadFailed(Picasso picasso, Uri uri, Exception e) {
Log.e("Picasso", "Failed to load image: " + uri.toString(), e);
}
})
.build();
}
}
或 CleanArchitecture 示例:
@Scope
@Retention(RUNTIME)
public @interface PerActivity {}
@PerActivity
@Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class)
public interface ActivityComponent {
//Exposed to sub-graphs.
Activity activity();
}
@Module
public class ActivityModule {
private final Activity activity;
public ActivityModule(Activity activity) {
this.activity = activity;
}
/**
* Expose the activity to dependents in the graph.
*/
@Provides @PerActivity Activity activity() {
return this.activity;
}
}
我可以清楚地看到这与 JSR-330 自定义范围有关,但我真的不明白这里到底发生了什么,所以这段代码使给定模块 and/or 由给定模块提供的内容取决于实际的 Activity
生命周期,并且仅存在一个实例,但前提是给定 activity 处于活动状态。
文档是这样说的:
Scope
Dagger 1 only supported a single scope: @Singleton.
Dagger 2 allows users to any well-formed scope annotation.
The Component docs describe the details of
how to properly apply scope to a component.
它说要看 Component docs page, but that gives me 404. I also saw this,但是...
我可以寻求一些帮助来澄清为什么指定此自定义范围神奇地使 Activity-level scopes
正常工作吗?
(答案是,一个子作用域可以从它的超级作用域接收依赖关系,只要组件存在,子作用域就存在。而且你需要在你的模块上指定作用域,你需要指定你的组件依赖关系子作用域 一个 超级作用域。)
其实没有魔法。自定义范围注解只是注解。他们可以有任何名字。
作用域的第一个功能是告诉 Dagger 编译器在作用域组件中允许哪些作用域。这就是为什么在非 @ActivityScope
组件中使用 @ActivityScope
依赖项会引发编译错误。
事实上,组件可以声明许多作用域(例如 @ActivityScope
和 @UiScope
),Dagger 会将它们视为单一作用域——这称为作用域别名。例如,它在多模块项目中很有用——当一个 Gradle 模块用它的 Dagger 模块定义一个作用域而另一个 Gradle 模块定义另一个作用域时,而在某些情况下它们都可以用作单个别名作用域第三个 Gradle 定义 Dagger 组件的模块。
第二个功能是限制作用域组件中允许的实例数。支持多种类型的范围:
Unscoped - 当没有声明注解时。未限定范围的依赖项将生成简单的 Provider
,无需任何缓存,并且在组件中创建的该依赖项的任何实例对于每次新注入都是新的(如在构造函数中,或在模块提供方法中,或只是作为一个字段)。
自定义范围 例如@ActivityScope
使用 @javax.inject.Scope
注释定义的注释 - 使用该范围声明的依赖项具有缓存 Provider
并生成双重检查锁,并且只会在使用相同范围声明的组件中为其创建单个实例它的创建将是线程安全的。请注意,对于组件本身的每个实例,都会创建该依赖项的新实例。
可重用范围 - 使用 @dagger.Reusable
注释声明 - 使用该范围声明的依赖项可以通过公共父组件在不同组件之间共享,并将具有缓存 Provider
生成单检查锁。当依赖项不一定需要具有单个实例但可以共享以提高单个组件或组件之间的性能(减少分配)时,它很有用。
有关作用域如何工作的更多信息,请参阅 user's guide 和 Dagger 生成的代码。
如何定义实际范围是你的特权。定义范围组件的生命周期,创建时间和销毁时间 - 这就是您的范围。例如。 @ActivityScope
绑定到 Activity 生命周期并定义如下:
private ActivityComponent component;
@Override
protected void onCreate(Bundle savedInstanceState) {
component = DaggerActivityComponent.builder().build();
component.inject(this);
}
@Override
protected void onDestroy() {
component = null;
super.onDestroy();
}
所以没有魔法。通过使用它们的语义来定义您的范围。
您可能还会发现有用的 and these examples.
编辑 2018 年 10 月 14 日 扩展范围函数和类型以消除先前答案中的歧义。
值得注意的是,显然 Dagger2 在每个组件的模块中为每个作用域提供程序创建了一个实例。
因此,为了在模块中获得范围内的提供程序,您需要为模块的提供程序方法指定范围。
@Module
public class YourModule {
@Provides
@YourScope //one per component
public Something something() { return new SomethingImpl(); }
@Provides //new instance per injection
public Otherthing otherthing() { return new OtherthingImpl(); }
}
@Component
@YourScope
public interface YourComponent {
Something something();
Otherthing otherthing();
void inject(YourThing yourThing); // only if you want field injection
}
编辑开始:虽然一般来说,您不需要在模块中实例化您自己的实现,因此您实际上可以这样做:
@Module
public abstract class YourModule {
@Binds
@YourScope //one per component
public abstract Something something(SomethingImpl impl);
@Binds //normally new instance per injection, depends on scope of Impl
public abstract Otherthing otherthing(OtherthingImpl impl);
}
@Singleton
public class SomethingImpl implements Something {
@Inject
SomethingImpl() {
}
}
// unscoped
public class OtherThingImpl implements OtherThing {
@Inject
OtherThingImpl() {
}
}
@Component
@YourScope
public interface YourComponent {
Something something();
Otherthing otherthing();
void inject(YourThing yourThing); // only if you want field injection
}
编辑结束
之后参考基里尔的回答;本质上 "scope" 本身仅确定它是与另一个不同的范围。使用组件依赖项(或子组件)创建子范围。
@Module
public class SubModule {
@Provides
@SubScope
public ThatThing thatThing() { return new ThatThingImpl(); }
}
@Component(dependencies={YourComponent.class}, modules={SubModule.class})
@SubScope
public interface SubComponent extends YourComponent {
ThatThing thatThing();
void inject(SubThing subThing); // only if you want field injection
}
一个组件只能依赖一个其他范围内的组件。
我正在阅读具有 @PerActivity
作用域的 Dagger2 Component Scopes Test on GitHub, and I've seen a "custom scope" defined for activities called @ActivityScope
, but I've seen it in other projects including the 4-module CleanArchitecture 的源代码。
但从字面上看,@ActivityScope
注释的代码如下:
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import javax.inject.Scope;
/**
* Created by joesteele on 2/15/15.
*/
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {
}
它 "magically" 可用于模块:
@Module
public class ActivityModule {
@Provides @ActivityScope Picasso providePicasso(ComponentTest app, OkHttpClient client) {
return new Picasso.Builder(app)
.downloader(new OkHttpDownloader(client))
.listener(new Picasso.Listener() {
@Override public void onImageLoadFailed(Picasso picasso, Uri uri, Exception e) {
Log.e("Picasso", "Failed to load image: " + uri.toString(), e);
}
})
.build();
}
}
或 CleanArchitecture 示例:
@Scope
@Retention(RUNTIME)
public @interface PerActivity {}
@PerActivity
@Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class)
public interface ActivityComponent {
//Exposed to sub-graphs.
Activity activity();
}
@Module
public class ActivityModule {
private final Activity activity;
public ActivityModule(Activity activity) {
this.activity = activity;
}
/**
* Expose the activity to dependents in the graph.
*/
@Provides @PerActivity Activity activity() {
return this.activity;
}
}
我可以清楚地看到这与 JSR-330 自定义范围有关,但我真的不明白这里到底发生了什么,所以这段代码使给定模块 and/or 由给定模块提供的内容取决于实际的 Activity
生命周期,并且仅存在一个实例,但前提是给定 activity 处于活动状态。
文档是这样说的:
Scope
Dagger 1 only supported a single scope: @Singleton.
Dagger 2 allows users to any well-formed scope annotation.
The Component docs describe the details of
how to properly apply scope to a component.
它说要看 Component docs page, but that gives me 404. I also saw this,但是...
我可以寻求一些帮助来澄清为什么指定此自定义范围神奇地使 Activity-level scopes
正常工作吗?
(答案是,一个子作用域可以从它的超级作用域接收依赖关系,只要组件存在,子作用域就存在。而且你需要在你的模块上指定作用域,你需要指定你的组件依赖关系子作用域 一个 超级作用域。)
其实没有魔法。自定义范围注解只是注解。他们可以有任何名字。
作用域的第一个功能是告诉 Dagger 编译器在作用域组件中允许哪些作用域。这就是为什么在非 @ActivityScope
组件中使用 @ActivityScope
依赖项会引发编译错误。
事实上,组件可以声明许多作用域(例如 @ActivityScope
和 @UiScope
),Dagger 会将它们视为单一作用域——这称为作用域别名。例如,它在多模块项目中很有用——当一个 Gradle 模块用它的 Dagger 模块定义一个作用域而另一个 Gradle 模块定义另一个作用域时,而在某些情况下它们都可以用作单个别名作用域第三个 Gradle 定义 Dagger 组件的模块。
第二个功能是限制作用域组件中允许的实例数。支持多种类型的范围:
Unscoped - 当没有声明注解时。未限定范围的依赖项将生成简单的 Provider
,无需任何缓存,并且在组件中创建的该依赖项的任何实例对于每次新注入都是新的(如在构造函数中,或在模块提供方法中,或只是作为一个字段)。
自定义范围 例如@ActivityScope
使用 @javax.inject.Scope
注释定义的注释 - 使用该范围声明的依赖项具有缓存 Provider
并生成双重检查锁,并且只会在使用相同范围声明的组件中为其创建单个实例它的创建将是线程安全的。请注意,对于组件本身的每个实例,都会创建该依赖项的新实例。
可重用范围 - 使用 @dagger.Reusable
注释声明 - 使用该范围声明的依赖项可以通过公共父组件在不同组件之间共享,并将具有缓存 Provider
生成单检查锁。当依赖项不一定需要具有单个实例但可以共享以提高单个组件或组件之间的性能(减少分配)时,它很有用。
有关作用域如何工作的更多信息,请参阅 user's guide 和 Dagger 生成的代码。
如何定义实际范围是你的特权。定义范围组件的生命周期,创建时间和销毁时间 - 这就是您的范围。例如。 @ActivityScope
绑定到 Activity 生命周期并定义如下:
private ActivityComponent component;
@Override
protected void onCreate(Bundle savedInstanceState) {
component = DaggerActivityComponent.builder().build();
component.inject(this);
}
@Override
protected void onDestroy() {
component = null;
super.onDestroy();
}
所以没有魔法。通过使用它们的语义来定义您的范围。
您可能还会发现有用的
编辑 2018 年 10 月 14 日 扩展范围函数和类型以消除先前答案中的歧义。
值得注意的是,显然 Dagger2 在每个组件的模块中为每个作用域提供程序创建了一个实例。
因此,为了在模块中获得范围内的提供程序,您需要为模块的提供程序方法指定范围。
@Module
public class YourModule {
@Provides
@YourScope //one per component
public Something something() { return new SomethingImpl(); }
@Provides //new instance per injection
public Otherthing otherthing() { return new OtherthingImpl(); }
}
@Component
@YourScope
public interface YourComponent {
Something something();
Otherthing otherthing();
void inject(YourThing yourThing); // only if you want field injection
}
编辑开始:虽然一般来说,您不需要在模块中实例化您自己的实现,因此您实际上可以这样做:
@Module
public abstract class YourModule {
@Binds
@YourScope //one per component
public abstract Something something(SomethingImpl impl);
@Binds //normally new instance per injection, depends on scope of Impl
public abstract Otherthing otherthing(OtherthingImpl impl);
}
@Singleton
public class SomethingImpl implements Something {
@Inject
SomethingImpl() {
}
}
// unscoped
public class OtherThingImpl implements OtherThing {
@Inject
OtherThingImpl() {
}
}
@Component
@YourScope
public interface YourComponent {
Something something();
Otherthing otherthing();
void inject(YourThing yourThing); // only if you want field injection
}
编辑结束
之后参考基里尔的回答;本质上 "scope" 本身仅确定它是与另一个不同的范围。使用组件依赖项(或子组件)创建子范围。
@Module
public class SubModule {
@Provides
@SubScope
public ThatThing thatThing() { return new ThatThingImpl(); }
}
@Component(dependencies={YourComponent.class}, modules={SubModule.class})
@SubScope
public interface SubComponent extends YourComponent {
ThatThing thatThing();
void inject(SubThing subThing); // only if you want field injection
}
一个组件只能依赖一个其他范围内的组件。