android dagger2使用心得

Dagger 2 是 Java 和 Android 下的一个完全静态、编译时生成代码的依赖注入框架,由 Google 维护,早期的版本 Dagger 是由 Square 创建的。

Dagger 2 是基于 Java Specification Request(JSR) 330标准。利用 JSR 注解在编译时生成代码,来注入实例完成依赖注入。

Dagger2 几个注解

  • @Inject 带有此注解的属性或构造方法将参与到依赖注入中,Dagger2会实例化有此注解的类。标注的成员属性不能是private

  • @Module 带有此注解的类,用来提供依赖,里面定义一些用@Provides注解的以provide开头的方法,这些方法就是所提供的依赖,Dagger2会在该类中寻找实例化某个类所需要的依赖。

  • @Component 用来将@Inject和@Module联系起来的桥梁,从@Module中获取依赖并将依赖注入给@Inject。只能标注接口或抽象类,声明的注入接口的参数类型必须和目标类一致。

约定俗成的是@Provides方法一般以provide为前缀,Moudle 类以Module为后缀,一个 Module 类中可以有多个@Provides方法。

模块与模块之间的联系

1
2
3
4
5
6
7
@Module (includes = {BModule.class})// includes 引入)
public class AModule {
@Provides
A providerA() {
return new A();
}
}

一个Component 应用多个 module

1
2
3
4
@Component(modules = {AModule.class,BModule.class})
public interface MainComponent {
void inject(MainActivity activity);
}

dependencies 依赖其他Component

1
2
3
4
@Component(modules = {MainModule.class}, dependencies = AppConponent.class)
public interface MainConponent {
void inject(MainActivity activity);
}

Provider 注入

有时候不仅仅是注入单个实例,我们需要多个实例,这时可以使用注入Provider,每次调用它的 get() 方法都会调用到 @Inject 构造函数创建新实例或者 Module 的 provide 方法返回实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class CarFactory {
@Inject
Provider<Car> carProvider;

public List<Car> makeCar(int num) {
...
List<Car> carList = new ArrayList<Car>(num);
for (int i = 0; i < num; i ++) {
carList.add(carProvider.get());
}
return carList;
}
}

Lazy 延迟注入

有时我们想注入的依赖在使用时再完成初始化,加快加载速度,就可以使用注入Lazy。只有在调用 Lazy 的 get() 方法时才会初始化依赖实例注入依赖。

1
2
3
4
5
6
7
8
9
10
public class Man {
@Inject
Lazy<Car> lazyCar;

public void goWork() {
...
lazyCar.get().go(); // lazyCar.get() 返回 Car 实例
...
}
}

Qualifier(限定符-别名)

试想这样一种情况:沿用之前的 Man 和 Car 的例子,如果 CarModule 提供了两个生成 Car 实例的 provide 方法,Dagger 2 在注入 Car 实例到 Man 中时应该选择哪一个方法呢?

1
2
3
4
5
6
7
8
9
10
11
12
@Module
public class CarModule {
@Provides
static Car provideCar1() {
return new Car1();
}
@Provides
static Car provideCar2() {
return new Car2();
}
// Car1 和 Car2 是 Car 的两个子类
}

这时 Dagger 2 不知道使用provideCar1还是provideCar2提供的实例,在编译时就会报错,这种情况也可以叫依赖迷失(网上看到的叫法)。而@Qualifier注解就是用来解决这个问题,使用注解来确定使用哪种 provide 方法。

下面是自定义的@Named注解,你也可以用自定义的其他 Qualifier 注解:

1
2
3
4
5
6
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
String value() default "";
}

在 provide 方法上加上@Named注解,用来区分

1
2
3
4
5
6
7
8
9
10
11
12
13
@Module
public class CarModule {
@Provides
@Named("car1")
static Car provideCar1() {
return new Car1();
}
@Provides
@Named("car2")
static Car provideCar2() {
return new Car2();
}
}

还需要在 Inject 注入的地方加上@Named注解,表明需要注入的是哪一种 Car:

1
2
3
4
5
6
public class Man {
@Inject
@Named("car1")
Car car;
...
}

这样在依赖注入时,Dagger 2 就会使用provideCar1方法提供的实例,所以Qualifier(限定符)的作用相当于起了个区分的别名

Scope

Scope 是用来确定注入的实例的生命周期的,如果没有使用 Scope 注解,Component 每次调用 Module 中的 provide 方法或 Inject 构造函数生成的工厂时都会创建一个新的实例,而使用 Scope 后可以复用之前的依赖实例。

在Dagger 2中
1、@Singleton可以保持类的单例。
2、@ApplicationScope注解的Component类与Applicaiton对象的生命周期一致。
3、@ActivityScope注解的Component类与Activity的生命周期一致
scope可以给我们带来“局部单例”,生命周期取决于scope自己。

在 Dagger 2 官方文档中我找到一句话,非常清楚地描述了@Scope的原理:
When a binding uses a scope annotation, that means that the component object holds a reference to the bound object until the component object itself is garbage-collected.
Scope 作用域的本质:Component 间接持有依赖实例的引用,把实例的作用域与 Component 绑定,它们不是同年同月同日生,但是同年同月死。

自定义@Scope

对于Android,我们通常会定义一个针对整个Activity的注解,通过仿照@Singleton

@Scope
@Documented
@Retention(RUNTIME)
public @interface ActivityScope {}

你可能会发现,这个自定义的@Scope和@Singleton代码完全一样,具有实现单例模式的功能。那干嘛还自定义@Scope,好处如下:

更好的管理ApplicationComponent和Module之间的关系,Component和Component之间的依赖和继承关系。如果关系不匹配,在编译期间会报错,详细下面会介绍。
代码可读性,让程序猿更好的了解Module中创建的类实例的使用范围。

Singleton Scope

@Singleton顾名思义保证单例

Reusable Scope

只单纯缓存依赖的实例,可以复用之前的实例,不关心与之绑定是什么 Component,Reusable 作用域只需要标记目标类或 provide 方法,不用标记 Component。

Releasable references(可释放引用)

使用 Scope 注解时,Component 会间接持有绑定的依赖实例的引用,也就是说实例在 Component 还存活时无法被回收。而在 Android 中,应该尽量减少内存占用,把没有使用的对象释放,这时可以用@CanReleaseReferences标记 Scope 注解。

然后在 Application 中注入ReleasableReferenceManager对象,在内存不足时调用releaseStrongReferences()方法把 Component 间接持有的强引用变为弱引用

1
2
3
4
5
6
7
8
9
10
11
12
public class MyApplication extends Application {
@Inject
@ForReleasableReferences(MyScope.class)
ReleasableReferenceManager myScopeReferences;

@Override
public void onLowMemory() {
super.onLowMemory();
myScopeReferences.releaseStrongReferences();
}
...
}

这样在内存不足时,Component间接持有的实例为弱引用,如果没有其他对象使用的话就可以被回收。

使用@Scope的一些经验:

1、@Component关联的@Module中的任何一个@Provides有@scope,则该整个@Component要加上这个scope。否则在暴露或者注入时(不暴露且不注入时,既不使用它构造对象时,不报错),会有如下错误:

Error:(13, 1) 错误: cn.xuexuan.newui.di.component.ActivityComponent (unscoped) may not reference scoped bindings:
@Singleton @Provides android.app.Activity cn.xuexuan.newui.di.module.ActivityModule.getActivity()

2、 @Component的dependencies与@Component自身的scope不能相同
@Singleton的组件不能依赖其他scope的组件,但是其他scope的组件可以依赖@Singleton组件;
没有scope的不能依赖有scope的组件
否则出现下面错误:

Error:(21, 1) 错误: com.android.example.devsummit.archdemo.di.component.MyTestComponent (unscoped) cannot depend on scoped components:
@com.android.example.devsummit.archdemo.di.scope.ActivityScope com.android.example.devsummit.archdemo.di.component.MyTestComponentX

3、一个component不能同时有多个scope(Subcomponent除外),否则出现下面的错误

Error:Execution failed for task ‘:app:compileDebugJavaWithJavac’.
java.lang.IllegalArgumentException: com.android.example.devsummit.archdemo.di.component.MyTestComponent was annotated with more than one @Scope annotation

@BindsInstance

Component 可以在创建 Component 的时候绑定依赖实例,用以注入。这就是@BindsInstance注解的作用,只能在 Component.Builder 中使用。

@Module
public final class HomeActivityModule {
    private final HomeActivity activity;

    public HomeActivityModule(HomeActivity activity) {
        this.activity = activity;
    }

    @Provides
    @ActivityScope  // 自定义作用域
    Activity provideActivity() {
        return activity;
    }
}

而使用@BindsInstance的话会更加简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
@ActivityScope
@Component
public interface HomeActivityComponent {

@Component.Builder
interface Builder {

@BindsInstance
Builder activity(Activity activity);

HomeActivityComponent build();
}
}

注意在调用build()创建 Component 之前,所有@BindsInstance方法必须先调用。上面例子中 HomeActivityComponent 还可以注入 Activity 类型的依赖,但是不能注入 HomeActivity,因为 Dagger 2 是使用具体类型作为依据的(也就是只能使用@Inject Activity activity而不是@Inject HomeActivity activity)。

如果@BindsInstance方法的参数可能为 null,需要再用@Nullable标记,同时标注 Inject 的地方也需要用@Nullable标记。这时 Builder 也可以不调用@BindsInstance方法,这样 Component 会默认设置 instance 为 null

注意:dagger.android 扩展库可以极大地简化在 Android 项目中使用 Dagger 2 的过程,但是还是有些限制,SubComponent.Builder 不能自定义 @BindsInstance 方法,SubCompoennt 的 Module 不能有含参数的构造函数,否则AndroidInjection.inject(this)在创建 SubComponent 时无法成功。

Component 的组织关系

Component 管理着依赖实例,根据依赖实例之间的关系就能确定 Component 的关系。这些关系可以用object graph描述,我称之为依赖关系图。在 Dagger 2 中 Component 的组织关系分为两种:

  • 依赖关系,一个 Component 依赖其他 Compoent 公开的依赖实例,用 Component 中的dependencies声明。

  • 继承关系,一个 Component 继承(也可以叫扩展)某 Component 提供更多的依赖,SubComponent 就是继承关系的体现。

依赖关系

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@ManScope
@Component(modules = CarModule.class)
public interface ManComponent {
void inject(Man man);

Car car(); //必须向外提供 car 依赖实例的接口,表明 Man 可以借 car 给别人
}

@FriendScope
@Component(dependencies = ManComponent.class)
public interface FriendComponent {
void inject(Friend friend);
}

ManComponent manComponent = DaggerManComponent.builder()
.build();

FriendComponent friendComponent = DaggerFriendComponent.builder()
.manComponent(manComponent)
.build();
friendComponent.inject(friend);

依赖关系就跟生活中的朋友关系相当,注意事项如下:

  1. 被依赖的 Component 需要把暴露的依赖实例用显式的接口声明,如上面的Car car(),我们只能使用朋友愿意分享的东西。

  2. 依赖关系中的 Component 的 Scope 不能相同,因为它们的生命周期不同。

继承关系

示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Module(subcomponents = SonComponent.class)
public class CarModule {
@Provides
@ManScope
static Car provideCar() {
return new Car();
}
}

@ManScope
@Component(modules = CarModule.class)
public interface ManComponent {
//void inject(Man man); // 继承关系中不用显式地提供暴露依赖实例的接口

SonComponent.Builder sonComponent(); // 用来创建 Subcomponent
}

@SonScope
@SubComponent(modules = BikeModule.class)
public interface SonComponent {
void inject(Son son);

@Subcomponent.Builder
interface Builder { // SubComponent 必须显式地声明 Subcomponent.Builder,parent Component 需要用 Builder 来创建 SubComponent
SonComponent build();
}
}

ManComponent manComponent = DaggerManComponent.builder()
.build();

SonComponent sonComponent = manComponent.sonComponent()
.build();
sonComponent.inject(son);

在 parent Component 依赖的 Module 中的subcomponents加上 SubComponent 的 class,然后就可以在 parent Component 中请求 SubComponent.Builder。但却无法访问 SubComponent 中的依赖。

继承关系和依赖关系最大的区别就是:继承关系中不用显式地提供依赖实例的接口,SubComponent 继承 parent Component 的所有依赖。

依赖关系 vs 继承关系

相同点:

  • 两者都能复用其他 Component 的依赖

  • 有依赖关系和继承关系的 Component 不能有相同的 Scope

区别:

  • 依赖关系中被依赖的 Component 必须显式地提供公开依赖实例的接口,而 SubComponent 默认继承 parent Component 的依赖。

  • 依赖关系会生成两个独立的 DaggerXXComponent 类,而 SubComponent 不会生成 独立的 DaggerXXComponent 类。

在 Android 开发中,Activity 是 App 运行中组件,Fragment 又是 Activity 一部分,这种组件化思想适合继承关系,所以在 Android 中一般使用 SubComponent。

重复的 Module

当相同的 Module 注入到 parent Component 和它的 SubComponent 中时,则每个 Component 都将自动使用这个 Module 的同一实例。也就是如果在 SubComponent.Builder 中调用相同的 Module 或者在返回 SubComponent 的抽象工厂方法中以重复 Module 作为参数时,会出现错误。(前者在编译时不能检测出,是运行时错误)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Component(modules = {RepeatedModule.class, ...})
interface ComponentOne {
ComponentTwo componentTwo(RepeatedModule repeatedModule); // 编译时报错
ComponentThree.Builder componentThreeBuilder();
}

@Subcomponent(modules = {RepeatedModule.class, ...})
interface ComponentTwo { ... }

@Subcomponent(modules = {RepeatedModule.class, ...})
interface ComponentThree {
@Subcomponent.Builder
interface Builder {
Builder repeatedModule(RepeatedModule repeatedModule);
ComponentThree build();
}
}

DaggerComponentOne.create().componentThreeBuilder()
.repeatedModule(new RepeatedModule()) // 运行时报错 UnsupportedOperationException!
.build();

dagger.android 扩展库的使用

dagger.android 扩展库是为了简化 Dagger 2 在 Android 的使用。

引入 dagger.android 扩展库

1
2
3
4
5
6
7
8
// dagger 2
implementation "com.google.dagger:dagger:$dagger_version"
kapt "com.google.dagger:dagger-compiler:$dagger_version"

// dagger.android
implementation "com.google.dagger:dagger-android:$dagger_version"
implementation "com.google.dagger:dagger-android-support:$dagger_version"
kapt "com.google.dagger:dagger-android-processor:$dagger_version"

从上面可以看出 dagger.android 扩展库有单独的注解处理器 dagger-android-processor。

注入 Activity 中的依赖

以 SearchActivity 为例,说明 dagger.android 的使用:

1.在 AppComponent 中安装 AndroidInjectionModule,确保包含四大组件和 Fragment 的注入器类型。

1
2
3
@Singleton
@Component(modules = [AppModule::class, AndroidInjectionModule::class])
interface AppComponent { ... }

2.Activity 对应的 SubComponent 实现 AndroidInjector 接口,对应的 @Subcomponent.Builder 继承 AndroidInjector.Builder

1
2
3
4
5
6
@ActivityScope
@Subcomponent
interface SearchActivitySubcomponent : AndroidInjector<SearchActivity> {
@Subcomponent.Builder
abstract class Builder : AndroidInjector.Builder<SearchActivity>()
}

3.在定义 SubComponent 后,添加一个 ActivityBindModule 用来绑定 subcomponent builder,并把该 module 安装到 AppComponent 中。

1
2
3
4
5
6
7
8
9
10
11
12
@Module(subcomponents = [SearchActivitySubcomponent::class])
abstract class ActivityBindModule {
@Binds
@IntoMap
@ActivityKey(SearchActivity::class)
abstract fun bindAndroidInjectorFactory(
builder: SearchActivitySubcomponent.Builder): AndroidInjector.Factory<out Activity>
}

@Singleton
@Component(modules = [AppModule::class, AndroidInjectionModule::class, ActivityBindModule::class])
interface AppComponent { ... }

如果 SubComponent 和其 Builder 没有其他方法或没有继承其他类型,可以使用 @ContributesAndroidInjector 注解简化第二步和第三步,在一个抽象 Module 中添加一个使用 @ContributesAndroidInjector 注解标记的返回具体的 Activity 类型的抽象方法,还可以在 ContributesAndroidInjector 注解中标明 SubComponent 需要安装的 module。如果 SubComponent 需要作用域,只需要标记在该方法上即可。

1
2
3
4
5
6
7
8
9
10
@Module
abstract class ActivityBindModule {
@ActivityScope
@ContributesAndroidInjector
abstract fun searchActivityInjector(): SearchActivity
}

@Singleton
@Component(modules = [AppModule::class, AndroidInjectionModule::class, ActivityBindModule::class])
interface AppComponent { ... }

4.Application 类实现 HasActivityInjector 接口,并且注入一个 DispatchingAndroidInjector 类型的依赖作为 activityInjector() 方法的返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class GankApp : Application(), HasActivityInjector {

@Inject
lateinit var dispatchingActivityInjector: DispatchingAndroidInjector<Activity>

override fun onCreate() {
super.onCreate()
DaggerAppComponent.builder()
.appModule(AppModule(this))
.build()
.inject(this)
}

override fun activityInjector() = dispatchingActivityInjector
}

5.最后在 onCreate)() 方法中,在 super.onCreate() 之前调用 AndroidInjection.inject(this)。

1
2
3
4
5
6
7
8
9
class SearchActivity : BaseActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)

...
}
}

使用 dagger.android 后 Activity 对应的 SubComponent 的定义简化如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Module
abstract class ActivityBindModule {
@ActivityScope
@ContributesAndroidInjector(modules = [FragmentBindModule::class])
abstract fun mainActivityInjector(): MainActivity

@ActivityScope
@ContributesAndroidInjector
abstract fun pictureActivityInjector(): PictureActivity

@ActivityScope
@ContributesAndroidInjector
abstract fun searchActivityInjector(): SearchActivity
}

其中 @ContributesAndroidInjector 注解可以解决之前的两个问题,不需要手动创建每一个 SubComponent,也不用在 parent componenet 声明对应的返回对应的 SubComponent.Builder 的接口,以后添加 SubComponent 只需要添加一个 ContributesAndroidInjector 抽象方法。

然后使用AndroidInjection.inject(this) 来简化注入依赖的过程,隐藏注入依赖的细节。

注入 Fragment 和其他三大组件也是类似,只是 ContributesAndroidInjector 抽象方法的返回值变了,HasActivityInjector 接口换做 HasFragmentInjector 等接口。

小结

dagger.android 扩展库可以极大地简化在 Android 项目中使用 Dagger 2 的过程,但是还是有些限制,SubComponent.Builder 不能自定义 @BindsInstance 方法,SubCompoennt 的 Module 不能有含参数的构造函数,否则AndroidInjection.inject(this)在创建 SubComponent 时无法成功。

在使用过 dagger.android 扩展库一段时间后,个人认为其设计非常优雅,简化了 SubComponent 的定义过程和依赖注入的过程,使得开发者可以专注于 Module 管理依赖对象,所以建议大家在 Android 项目中使用。

@Binds

@Binds:可以理解为关联,首先它是跟@Provides使用地方是一样的,不同的在于@Provides 注解的方法都是有具体实现的,而@Binds修饰的只有方法定义,并没有具体的实现的,在方法定义中方法参数必须是 返回值的实现类。这样创建实体类的地方就不用在Modules 中实现了,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
@Module
public abstract class AppModule {

@Binds
@Named("AppManager")
abstract IAppManager bindAppManager(AppManager appManager);

@Provides
static Retrofit provideRetrofit() {
return new Retrofit.Builder().build();
}

}

@Component(modules = AppModule.class)
public interface AppComponent {
Application application();

Retrofit retrofit();

void inject(Application application);

@Component.Builder
interface Builder {

@BindsInstance
Builder application(Application application);

AppComponent build();
}
}

public class AppManager implements IAppManager {

@Inject
Application application;

@Inject
public AppManager() {

}

}

public class MyApplication extends Application {

@Override
public void onCreate() {
super.onCreate();

// 注入
DaggerAppComponent.builder().application(this).build().inject(this);
}
}

Module 中不一定要具体实现,可以用@Binds关联实体,这样在编译过程中会自动创建Fractory 以及实现的,AccountManagerDelegate中还可以使用该Module中 @Provides 提供的实体类

这里使用 provideXXX() 方式提供接口实例,说明几点:

  • 方法中必须有参数且只能有一个,是接口的实现类
  • 实现类必须提供@Inject的构造或 Module中@Provides形式提供
  • 方法是抽象方法,不必写方法体
  • 使用@Binds代替@Provides
  • 由于方法是抽象的,所以类也要抽象或接口

@IntoSet

使用注入形式初始化 Set 集合时,可以在 Module 中多次定义一系列返回值类型相同的方法:

1
2
3
4
5
6
7
8
9
10
@Module
class AnimalModule {
@IntoSet
@Provides
fun provideElephant() = Animal("大象")

@Provides
@IntoSet
fun provideMonkey() = Animal("猴子")
}

Animal.kt 代码:

1
class Animal(var name: String)

上面的 provideElephant() 和 provideMonkey() 返回值类型都是 Animal,都使用了@IntoSet注释,多说一句,@Provides当然也要有,如果是接口用@Binds。
初始化并使用:

1
2
3
4
5
6
7
8
@Inject
lateinit var set: MutableSet<Animal>

...//代码省略

set.forEach {
Log.i("Main", "--- ${it.name} ---")
}

kotlin 里面 MutableSet 代替 Java 中的 Set,上面代码运行可以输出:

1
2
--- 大象 ---
--- 猴子 ---

@IntoMap

与@IntoSet区别不大,Map 多了一个 key

1
2
3
4
5
6
7
8
9
@Provides
@IntoMap
@IntKey(0)
fun provideFish() = Animal("鱼")

@Provides
@IntoMap
@IntKey(1)
fun provideHuman() = Animal("人")

@IntKey里面就是 Map 中的 key,providesXXX() 返回值是 key 对应的 value,如果 key 是 String 类型的,则使用@StringKey()输入 key,此外,还可以自定义 key:

1
2
3
4
5
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@MapKey
annotation class ZhuangBiKey(val f: Float)

初始化 map 并使用:

1
2
3
4
5
6
7
8
@Inject
lateinit var map: Map<Int, Animal>

...//代码省略

map.forEach {
Log.i("Main", "--- key:${it.key}\nvalue:${it.value.name} ---")
}

参考资料

Dagger 2 完全解析
打破Dagger2使用窘境:Dagger-Android详解https://github.com/qingmei2/Sample_dagger2)
dagger组件化