Dagger2自定义范围:自定义范围(@ActivityScope)如何实际工作?

Epi*_*rce 42 android scope android-activity dagger-2

我正在阅读GitHub上的Dagger2组件范围测试的源代码,我看到为被调用的活动定义了"自定义范围" @ActivityScope,但我在其他项目中看到了它,包括具有其范围的4模块CleanArchitecture@PerActivity.

但从字面上看,@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 {
}
Run Code Online (Sandbox Code Playgroud)

它在模块中"神奇地"可用:

@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();
  }
}
Run Code Online (Sandbox Code Playgroud)

或者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;
  }
}
Run Code Online (Sandbox Code Playgroud)

我可以清楚地看到这与JSR-330自定义作用域有关,但我真的不明白这里到底发生了什么,以便这个代码能够使给定模块和/或给定模块提供的内容取决于实际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.
Run Code Online (Sandbox Code Playgroud)

它说看看组件文档页面,但这给了我404.我也看到了这个,但......

我可以请求帮助澄清为什么指定这个自定义范围会神奇地使Activity-level scopes工作没有问题?

(答案是,子范围可以从其超级作用域接收依赖关系,并且只要组件有子视图就存在.并且您需要在模块上指定范围,并且需要指定组件依赖关系以包含一个超级范围. )

Kir*_*nov 33

其实没有魔力.自定义范围注释只是注释.他们可以有任何名字.

范围的第一个功能是告诉Dagger编译器在范围内的组件中允许哪些范围.这就是为什么@ActivityScope在非@ActivityScope组件中使用依赖项会触发编译错误.

事实上,组件可以声明许多范围(例如@ActivityScope@UiScope),Dagger会将它们都视为单个范围 - 它称为范围别名.例如,它在多模块项目中很有用 - 当一个Gradle模块使用其Dagger模块定义一个范围而另一个Gradle模块定义另一个范围时,它们都可以在定义Dagger组件的第三个Gradle模块中用作单个别名范围.

第二个功能是限制范围组件中允许的实例数.支持几种类型的范围:

无范围 - 未声明注释时.无范围的依赖关系将在Provider没有任何缓存的情况下生成简单,并且在组件中创建的任何依赖关系的实例对于每次新注入都是新的(如在构造函数中,或在模块提供方法中,或者仅作为字段).

自定义范围,例如使用@ActivityScope注释定义的@javax.inject.Scope注释 - 使用该范围声明的依赖关系具有Provider生成双检查锁的缓存,并且将在使用相同范围声明的组件中为其创建单个实例,并且其创建将是线程安全的.请注意,对于组件本身的每个实例,都将创建该依赖项的新实例.

可重用范围 - 使用@dagger.Reusable注释声明 - 使用该范围声明的依赖关系可以通过公共父组件在不同组件之间共享,并且将Provider生成具有单一检查锁的缓存.当依赖关系不一定需要具有单个实例但可以共享以在单个组件中或组件之间提高性能(更少分配)时,它是有用的.

有关范围工作方式的更多信息,请参阅用户指南和Dagger生成的代码.

如何定义实际范围是您的特权.定义范围组件的实时周期,创建时和销毁时 - 这是您的范围.例如@ActivityScope,绑定到活动生命周期并定义如下:

private ActivityComponent component;

@Override
protected void onCreate(Bundle savedInstanceState) {
    component = DaggerActivityComponent.builder().build();
    component.inject(this);
}

@Override
protected void onDestroy() {
    component = null;
    super.onDestroy();
}
Run Code Online (Sandbox Code Playgroud)

所以没有魔力.通过使用它们的语义来定义范围.你也可以找到有用的答案这些例子.

编辑14.10.2018扩展了范围函数和类型,以消除先前答案中的歧义.

  • 为什么有必要设置`component = null`? (5认同)

Epi*_*rce 18

值得注意的是,显然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
}
Run Code Online (Sandbox Code Playgroud)

编辑开始:虽然通常,您不需要在模块中实例化您自己的实现,因此您实际上可以这样做:

@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
}
Run Code Online (Sandbox Code Playgroud)

编辑结束

然后,参考基里尔的答案; 本质上,"范围"本身仅确定它与另一个范围不同.使用组件依赖项(或子组件)创建子范围.

@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
}
Run Code Online (Sandbox Code Playgroud)

组件只能依赖于其他一个范围组件.

  • @EpicPandaForce 嗨,你是对的。我的回答模棱两可。通过“进行静态分析”,我的意思是 dagger 编译器将检查作用域组件内的作用域依赖项,除非使用作用域别名,否则未知作用域将被视为错误。我将编辑我的答案以扩展这一点。 (2认同)