Dagger2学习

What

关于IOC(控制反转)DI(依赖注入)DI只是IOC的实现的一种方式,另一种是依赖查找,详细见 控制反转维基百科

完全静态的依赖注入框架。依赖注入的概念真的是到哪都有,以前学Spring的就天天看它,到了android还是跑不了,现在的dagger是以半静态注入的形式,说白了,通过注解处理器分析注解,生成java代码,将对象池和需要对象的地方连接起来,其实就和自己创键差不多,我就简单总结下怎么用吧(里面的概念我也挺晕乎的,注解处理器那里我也是挺头疼的。)

官网: Dagger users-guide (果然还是还是官方文档讲的好呃呃)

How

引入依赖

1
2
implementation 'com.google.dagger:dagger:2.20'
annotationProcessor 'com.google.dagger:dagger-compiler:2.20'

最简单的例子

首先需求是我需要一个对象。使用@Inject注解构造方法表示构造器注入

1
2
3
4
5
public class Apple {
@Inject
public Apple() {
}
}

编写注入器,用@Component注解的一个接口即可,里面有一个inject方法,参数类型是你要注入的地方。

1
2
3
4
@Component
public interface AppleComponet {
void inject(TestActivity activity);
}

注入,构建项目,然后生成许多类,我们只用在意形如DaggerAppleComponet这样以Dagger开头的生成器就行了。

同样用@Inject注解我要注入的对象,DaggerAppleComponet.builder().build().inject(this);这样就完成注入了

1
2
3
4
5
6
7
8
9
10
11
public class TestActivity extends AppCompatActivity {
@Inject Apple mApple;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerAppleComponet.builder().build().inject(this);
Log.d("TestActivity", "onCreate: " + mApple);
}
}

在依赖注入里面是会自动解决依赖的。比如

1
2
3
4
5
6
7
class A {
@Inject A(B){}
}

class B{
@Inject B(){}
}

此时A依赖B,在注入A的时候会自动取注入B。只需要二者存在注入形式就行。

使用module注入

构造器注入挺简单方便,但是不是所有的对象都能够构造器注入,比如第三方类实例,你就无法添加注解,还有比如Retrofit中动态生成的接口API对象,也不能。所以注入module登场,在这里面用于产出对象。

使用@Module注解类名,就表示这是一个module,可以有构造函数传入我们需要的依赖,比如Context

使用@Provides修饰方法,命名为:providesXX,就表示产出对象。

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
@Module
public class CommonModule {
private Context mContext;
public CommonModule(Context context) {
mContext = context;
}
@Provides
TextView providesTextView(Context context, Cat cat){
TextView textView = new TextView(context);
textView.setText(cat.sayHello());
return textView;
}
@Provides
Context providesContextContext() {
return mContext;
}
@Provides
Cat providesCat(Hello hello) {
return new Cat(hello);
}
@Provides
Hello providesHello() {
return new Hello();
}
}

同样需要声明注入器

和上面不同的是需要,写入Module类型

1
2
3
4
@Component(modules = {CommonModule.class})
public interface HelloCompont {
void inject(TestDagger2Activity activity);
}

注入

1
2
3
4
5
6
7
8
9
10
11
12
public class TestDagger2Activity extends AppCompatActivity{
private static final String TAG = TestDagger2Activity.class.getSimpleName();
@Inject
TextView mTextView;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DaggerHelloCompont.builder().commonModule(new CommonModule(this)).build().inject(this);
setContentView(mTextView);
}
}

这只是另一种注入方式而已。

注解说明

@Component

注解接口,标注为注入器,构建后会生成前缀Dagger的类,用于注入

@Module

注解类,标注为注入模型,用于生成模型注入对象

@Inject

  • 注解构造方法表示,构造器注入

  • 注解变量,表示被注入的引用,访问限定不能为private

  • 不可以修饰接口,即使是通过Provides产出的接口类型,也只是能够通过方法依赖传递,并不能注入到标注了@Inject的引用上去

    因为我遇到了过这个问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    //大概举例
    interface A {}
    //通过Retrofit生成A
    A providesA(Retrofit retrofit) {
    return retrofit.create(A.class);
    }
    //此时我们是无法通过@Inject获得A的
    @Inject A;//无效
    //包装下
    class DataModel {
    A mA;
    @Inject
    DataModel(A a) {
    mA = a;
    }
    public A getA() {
    return mA;
    }
    }

    //此时我就可以这样
    @Inject DataModel mModel;
    A a = mModel.getA();

@Provides

在模型中,注解方法,一般规范以provides前缀开头方法,表示产出某对象

@Named

可能存在同种类型的对象,需要通过命名区分,其实@Named也是通过@Qualifier定义的。

1
2
3
4
5
6
7
8
9
10
11
@Named("one")
@Provides
Hello providesHello() {
return new Hello();//产出
}

@Named("tow")
@Provides
Hello providesHello() {
return new Hello();//产出
}

方法可以是静态的,这就意味着Module不需要实例化传入compoent

@Qualifier

修饰注解,来标注具体那个对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Qualifier
@interface A{
}
@Qualifier
@interface B{
}

@A
@Provides
Hello providesHello() {
return new Hello();//产出
}

@B
@Provides
Hello providesHello() {
return new Hello();//产出
}

@Scope

修饰注解,用于生成标注注入对象的作用域单例的注解

例如以下就生成了一个@PerFragment的作用域注解,在这个作用域内,@PerFragment修饰的对象为单例,即局部单例,亦或者搭配限定符使用,产出不同限定的单例

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

然后将该注解用于修饰注入器,和产出对象的地方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@PerFragment
@Subcomponent(modules = HomeFragmentModule.class)
public interface HomeFragmentComponent {
void inject(HomeFragment homeFragment);
}

@Module
public class TreeArticlesFragmentModule {

@PerFragment
@Provides
List<Article> providesArticles() {
return new ArrayList<>();
}
}

@Singleton

这个注解也是官方通过@Scope实现的,直接表明单例

1
2
3
4
@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}

懒加载

默认会在调用前就将对象注入到引用,使用以下形式可以懒加载,不会立马注入对象,只有在lazy.get()时才会去获得对象

1
2
@Inject
Lazy<Object> object;

Provider

有时候许多要不断注入同一类型的对象比如

1
2
3
4
5
6
7
8
@Inject A a1;
@Inject A a2;
....

List<A> list;

list.add(a1);
list.add(a2);

像这样需要不止一个对象,这时候就需要使用Provider了

1
2
3
4
5
6
7
8
9
10
@Inject Provider<Filter> filterProvider;
public void brew(int numberOfPots) {
...
for (int p = 0; p < numberOfPots; p++) {
maker.addFilter(filterProvider.get()); //每次都会去获得新的对象
maker.addCoffee(...);
maker.percolate();
...
}
}

Componet注入器之间关系

之前的简单场景都是仅限于当前注入环境,倘若我们又这个需求,比如Fragment需要用到Activity的对象,亦或者全局环境需要用到全局注入对象,这个时候前面的基础操作就不管用了。因为它只关心注入的当前环境Tinject(T),离开了这个环境你也就无法获得注入对象了。

所有我们就得需要以下几个概念

注入器之间相互依赖

依赖就是,A的工作需要B的帮忙,也就能名正言顺的获得B注入器的注入对象

1
A(B b);

在注入器我们可这样声明依赖的注入器,通过在@Componet添加依赖的注入器,可以添加很多的

1
2
3
4
5
@PerFragment
@Component(modules = LoginFragmentModule.class, dependencies = AppComponent.class)
public interface LoginFragmentComponent {
void inject(LoginFragment fragment);
}

注入过程,既然是已依赖的方式注入,就得要拿到依赖的注入器对象。

1
2
3
4
DaggerLoginFragmentComponent.builder()
.appComponent(App.getAppComponent())
.build()
.inject(this);

如果你想获得依赖的对象,必须要在注入器暴露接口,才能打开一个通往注入器依赖的入口

1
2
3
4
5
6
7
@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {
void inject(App app);
App getApp();
DataModel getDataModel();//暴露
}

这样就能名正言顺的使用依赖注入器的依赖对象了。

注入器继承

第二种方式,比如一个Activity有很多的Fragment,这个时候使用继承关系来获取注入器内容更好,不需要暴露依赖对象接口

看父注入器声明

1
2
3
4
5
6
@PerActivity
@Component(dependencies = AppComponent.class, modules = MainActivityModule.class)
public interface MainActivityComponent {
void inject(MainActivity mainActivity);
HomeFragmentComponent getHomeFragmentComponent(HomeFragmentModule homeFragmentModule);//提供子注入器
}

子注入器声明,使用@SubComponet表明这个子注入器

1
2
3
4
5
@PerFragment
@Subcomponent(modules = HomeFragmentModule.class)
public interface HomeFragmentComponent {
void inject(HomeFragment homeFragment);
}

注入

1
2
3
4
((MainActivity) mActivity)
.getComponent()//通过Activity获得父注入器
.getHomeFragmentComponent(new HomeFragmentModule())//通过父注入器获得子注入器并且传入Modele
.inject(this);

通过继承的方式将应用对象图谱分割成不同的部分,子注入器可以依赖祖先注入器的绑定对象,反之父注入器不能依赖子注入器绑定对象,同级注入器也无法依赖。换句话说父注入器的对象图是子注入器的对象图的子对象图。

呃呃好像官方文档使用Builder另一种方式写的SubComponent

这种基于Builder写起来真麻烦啊

在Component提供依赖

这样主动暴露依赖,其实去工厂里拿

1
2
3
4
@Componet
public interface AComponet{
Apple getApple();
}

Multibindings

允许将绑定对象进集合

绑定进Set

使用@IntoSet表示将绑定对象输入set

1
2
3
4
5
6
7
@Module
class MyModuleA {
@Provides @IntoSet
static String provideOneString(DepA depA, DepB depB) {
return "ABC";
}
}

使用@ElementsIntoSet表示将一个set输送进set

1
2
3
4
5
6
7
@Module
class MyModuleB {
@Provides @ElementsIntoSet
static Set<String> provideSomeStrings(DepA depA, DepB depB) {
return new HashSet<String>(Arrays.asList("DEF", "GHI"));
}
}

绑定好的set可以解决set的依赖

1
2
3
4
5
6
7
class Bar {
@Inject Bar(Set<String> strings) {
assert strings.contains("ABC");
assert strings.contains("DEF");
assert strings.contains("GHI");
}
}

或者这样使用

1
2
3
4
5
6
7
8
9
@Component(modules = {MyModuleA.class, MyModuleB.class})
interface MyComponent {
Set<String> strings();
}

@Test void testMyComponent() {
MyComponent myComponent = DaggerMyComponent.create();
assertThat(myComponent.strings()).containsExactly("ABC", "DEF", "GHI");
}

可以结合 Set<Foo>以及 Provider<Set<Foo>> or Lazy<Set<Foo>>这样的形式,但是不能Set<Provider<Foo>>

同样可以结合@Qualifier来限定

绑定进Map

还可以绑定进map,好吧本来我想看到上面就写的。下面其实就是看到官方文档上的23333

普通使用

使用@IntoMap注解进入map的对象,使用@StringKey表示String类型的key,使用@ClassKey表示Class类型的key

之后我们就可获得Map<String, Long>的Map

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
@Module
class MyModule {
@Provides @IntoMap
@StringKey("foo")
static Long provideFooValue() {
return 100L;
}

@Provides @IntoMap
@ClassKey(Thing.class)
static String provideThingValue() {
return "value for Thing";
}
}

@Component(modules = MyModule.class)
interface MyComponent {
Map<String, Long> longsByString();
Map<Class<?>, String> stringsByClass();
}

@Test void testMyComponent() {
MyComponent myComponent = DaggerMyComponent.create();
assertThat(myComponent.longsByString().get("foo")).isEqualTo(100L);
assertThat(myComponent.stringsByClass().get(Thing.class))
.isEqualTo("value for Thing");
}
枚举和带泛型的KEY

通过@MapKey来自定以枚举key,以及符合泛型通配的key

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
enum MyEnum {
ABC, DEF;
}

@MapKey
@interface MyEnumKey {
MyEnum value();
}

@MapKey
@interface MyNumberClassKey {
Class<? extends Number> value();
}

@Module
class MyModule {
@Provides @IntoMap
@MyEnumKey(MyEnum.ABC)
static String provideABCValue() {
return "value for ABC";
}

@Provides @IntoMap
@MyNumberClassKey(BigDecimal.class)
static String provideBigDecimalValue() {
return "value for BigDecimal";
}
}

@Component(modules = MyModule.class)
interface MyComponent {
Map<MyEnum, String> myEnumStringMap();
Map<Class<? extends Number>, String> stringsByNumberClass();
}

@Test void testMyComponent() {
MyComponent myComponent = DaggerMyComponent.create();
assertThat(myComponent.myEnumStringMap().get(MyEnum.ABC)).isEqualTo("value for ABC");
assertThat(myComponent.stringsByNumberClass.get(BigDecimal.class))
.isEqualTo("value for BigDecimal");
}
更加复杂的KEY
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@MapKey(unwrapValue = false)
@interface MyKey {
String name();
Class<?> implementingClass();
int[] thresholds();
}

@Module
class MyModule {
@Provides @IntoMap
@MyKey(name = "abc", implementingClass = Abc.class, thresholds = {1, 5, 10})
static String provideAbc1510Value() {
return "foo";
}
}

@Component(modules = MyModule.class)
interface MyComponent {
Map<MyKey, String> myKeyStringMap();
}

对于复杂的Key来说,编译时是无法具体确认的,考虑使用Set<Map.KeyEntry>来完成同样可以达到map效果

上面的只是讲了个大概目前用到,其实还有关于子注入器更多的知识和Producers的内容,以后再补充吧。

Why

参考