Dagger 2模组的扩展方法

模组依赖模式与子模组模式

在前一篇文章中使用的模组只有一个且其生命周期持续整个app。

不过当我们希望存在多个模组来保证内存不被一直占用时(即将模组限制在某个生命周期内),我们可以使用模组依赖或者子模组。这两个模式各有各的封装方式。

使用模组依赖或者子模组有三个需要注意的地方:

  • 模组依赖模式需要父模组明确列出子模组需要的依赖,不过子模组模式不需要这样做。

  • 两个依赖模组不能使用同一个作用域。如两个存在扩展关系的Component不能同时使用@Singleton注释。

  • Dagger 2允许自定义作用域注释,而你的任务就是按作用域的定义去实现实例引用的创建和删除。因为实际上Dagger 2并不知道你定义的注释怎么实现。

模组依赖模式(Dependent Components)

假如我们想将模组生命周期限制在用户只处于登录状态时,可以先定义一个作用域注释。

1
2
3
4
5
@Scope
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface UserScope {
}

接下来开始定义父模组(即被依赖的模组)。

1
2
3
4
5
6
7
8
@Component(modules = {NetModule.class, ApplicationModule.class})
public interface NetComponent {
// 注射方法被移到了依赖模组中
// 依赖模组需要被依赖模组暴露需要的引用
// 方法名并没有硬性要求,重要的只是返回类型
Retrofit retrofit();
}

然后定义依赖模组。

1
2
3
4
5
@UserScope // 使用之前定义的作用域注释
@Component(dependencies = NetComponent.class, modules = GithubModule.class)
public interface GithubComponent {
void inject(MainActivity mainActivity);
}

然后创建一个简单的模块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Module
public class GithubModule {
public interface GithubInterface{
@GET("/users/{user}/repos")
Call<List<Repo>> getRepoList(@Path("user") String username, @QueryMap Map<String, String> options);
}
@UserScope // 需要与依赖模块中使用得作用域注释一致
@Provides
public GithubInterface providesGithubInterface(Retrofit retrofit) {
return retrofit.create(GithubInterface.class);
}
}

就像开头说的,模组依赖模式需要在父模组中明确列出子模组需要的依赖。这里模块中需要Retrofit引用,所以在父模组中定义了一个返回改引用的方法。

如果子模组来执行注射操作的话,父模组就不需要定义注释的方法了,所以inject方法放到了子模组中。

最后一步是用GithubComponent完成安装。不过这次,我们首先还得创建NetComponent来提供给DaggerGithubComponent的构造函数。

1
2
3
4
5
6
7
8
9
NetComponent netComponent = DaggerNetComponent.builder()
.applicationModule(new ApplicationModule(this))
.netModule(new NetModule("https://api.github.com"))
.build();
GithubComponent githubComponent = DaggerGithubComponent.builder()
.netComponent(netComponent)
.githubModule(new GithubModule())
.build();

子模组模式(Subcomponents)

使用子模组模式的一个好处就是不用在父模组中定义子模组需要的依赖。另一个与模组依赖模式不同的地方是其需要在父模组中声明一个工厂方法。

先创建一个以Activity为生命周期的子模组,加上自定义的作用域注释和@Subcomponent注释。

1
2
3
4
5
6
@GithubScope
@Subcomponent(modules = GithubModule.class)
public interface GithubSubComponent {
void inject(MainActivity mainActivity);
}

使用得GithubModule如下定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Module
public class GithubModule {
public interface GithubInterface{
@GET("/users/{user}/repos")
Call<List<Repo>> getRepoList(@Path("user") String username, @QueryMap Map<String, String> options);
}
@GithubScope
@Provides
public GithubInterface providesGithubInterface(Retrofit retrofit) {
return retrofit.create(GithubInterface.class);
}
}

最后在父模组中定义一个返回值为子模组的工厂方法:

1
2
3
4
@Component(modules = {NetModule.class, ApplicationModule.class})
public interface NetComponent {
GithubSubComponent githubSubComponent(GithubModule githubModule);
}

每当githubSubComponent方法被调用时都会生成一个GithubSubComponent的新实例。使用方法如下:

1
2
3
4
5
6
7
NetComponent netComponent = DaggerNetComponent.builder()
.applicationModule(new ApplicationModule(this))
.netModule(new NetModule("https://api.github.com"))
.build();
netComponent.githubSubComponent(new GithubModule())
.inject(this);

子模组生成器

从v2.7开始适用

通过使用子模组生成器模式,子模组的创建与父模组进行了解耦,因为没有继续父模组中声明子模组工厂方法的必要了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@GithubScope
@Subcomponent(modules = GithubModule.class)
public interface GithubSubComponent {
void inject(MainActivity mainActivity);
@Subcomponent.Builder
interface Builder extends SubcomponentBuilder<GithubSubComponent> {
Builder githubModule(GithubModule module);
}
interface SubcomponentBuilder<V> {
V build();
}
}

在子模组接口中必须包含一个内部接口,并为其定义一个build()方法。然后创建一个Builder接口,其返回值必须匹配子模组。

然后使用一个包含subcomponentbinder模块将Builder添加到父模组中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Module(subcomponents = GithubSubComponent.class)
public abstract class ApplicationBinders {
@Binds
@IntoMap
@SubcomponentKey(GithubSubComponent.Builder.class)
public abstract GithubSubComponent.SubcomponentBuilder mainActivity(GithubSubComponent.Builder impl);
}
@Component(modules = {NetModule.class, ApplicationModule.class, ApplicationBinders.class})
public interface NetComponent {
Map<Class<?>, Provider<GithubSubComponent.SubcomponentBuilder>> subcomponentBuilders();
}
@MapKey
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SubcomponentKey {
Class<?> value();
}

当生成器在父模块中可用后就可以使用其创建子模块了。

1
2
3
4
5
6
7
8
9
GithubSubComponent.Builder builder = (GithubSubComponent.Builder)
((MyApplication) getApplication()).getNetComponent()
.subcomponentBuilders()
.get(GithubSubComponent.Builder.class)
.get();
builder.githubModule(new GithubModule())
.build()
.inject(this);

Demo项目地址

Demo

这个项目有多个分支,与本文相关的为dependent_component,subcomponent,subcomponent_version_above_2.7三个分支。

参考文献