Dagger2总结

coffee

简介

这是一个DI(依赖注入)框架,JSR330是依赖注入的规范,在服务器端有spring实现,dagger2使用注解实现

Dagger2解决了什么问题

  1. 没有用到反射,编译期生成静态代码,生成一些Factory模板代码,性能上对比Guice,Dagger1提高
  2. 解决了依赖的顺序问题。当有多个类需要初始化(比如A先初始化,B后初始化,A作为B的入参),有依赖的时候要小心顺序问题
  3. 不用自己写单例(单例写法没办法抽象),少写很多Factory模板代码
  4. 需要用到的时候@Inject,解耦、方便

基本使用

所有代码托管在git@osc上Dagger2Learn

简单注入

假设MainActivity里有一个NuclearController。现在想用依赖注入,注入到MainActvity里

1
2
3
4
5
6
public class NuclearController {
@Inject
public NuclearController() {
//dosomething
}
}

这里NuclearController是无参数构造,所以不需要Module提供资源。MainActivityComponent如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public interface MainActivityComponent {
void inject(MainActivity activity);
}
//MainActivity里使用--------------------
@Inject
NuclearController nuclearController;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerMainActivityComponent.builder().build().inject(this);
Preconditions.checkNotNull(nuclearController);
}

记得在DaggerMainActivityComponent调用之前Build一下, 按照写代码的顺序,先写自己的模块/layer,然后写Component,Component的作用是桥梁
用来连接你在Activity里@Inject的地方和在NuclearController构造函数上的@Inject。这是最简单的例子,通常我们的模型不可能空参,而且会依赖各种其他模型

Module提供依赖资源

用@Module标注的类,可以为依赖提供参数,比如说此时NuclearController的构造函数变成

1
2
3
4
@Inject
public NuclearController(long electricity) {
this.electricity = electricity;
}

我们需要给一个module用来提供构造需要的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Module
public class MainModule {
private long electricity;

public MainModule(long electricity){
this.electricity = electricity;
}

@Provides
public long provideElectry() {
return electricity;
}
}
//MainActivity调用--------------------
DaggerMainActivityComponent.builder().mainModule(new MainModule(10000)).build().inject(this);

用@Module修饰类,用@Provides修饰方法,提供需要的资源。对比更改前的代码,只是改了module和注入的地方,改动相当少
所以Component就是桥梁,将Controller和Activity连接起来,Module就是Provider,提供这个连接过程中需要的依赖参数

Scope Ⅰ

换一个实际的例子来说明Scope,Component的概念

.
|-- HeroApplication.java
|-- common
|   |-- AppComponent.java
|   `-- AppModule.java

dagger2自带一个@Singleton,看看源码:

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

并没有什么特殊的东西,所以我们自定义Scope只需要换一个名字就行了。Scope代表一种约束,这么说,所有Scope都可以看做是单例,但是不同于我们常规的单例,Scope在哪里用就在哪里是单例,生命周期需要我们自己处理。
比如AppComponent和AppModule都是全局性的,跟随应用生命周期的,我们用Singleton修饰

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
@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {
Application getApplication();
Context getApplicationContext();
}
//--------------------
@Module
public class AppModule {
private Application application;

public AppModule(Application application) {
this.application = application;
}

@Provides
@Singleton
public Application provideApplication() {
return application;
}

@Provides
@Singleton
public Context provideApplicationContext() {
return application.getApplicationContext();
}
}
//------------------在Application里初始化依赖
public class HeroApplication extends Application {

private AppComponent appComponent;

@Override
public void onCreate() {
super.onCreate();
appComponent = DaggerAppComponent.builder().appModule(new AppModule(this)).build();
}
public AppComponent getAppComponent() {
return appComponent;
}
}

AppComponent虽然我们用Singleton修饰了,但是如果不在Application里提供一个get方法,这个Singleton的实例就没有用,接下来可以通过Application.getAppComponent使用了(只做理解,这个例子并没有什么卵用)
Scope需要注意一点,一旦Component修饰了Scope,Module提供的方法上就必须声明同样的Scope

Scope Ⅱ & dependencies

为了说明Scope生命周期需要自己定义,我们添加BasicComponent,提供App开发过程中一些基础模型

.
|-- HeroApplication.java
|-- common
|   |-- ActivityScope.java
|   |-- ApiService.java
|   |-- AppComponent.java
|   |-- AppModule.java
|   |-- BasicComponent.java
|   |-- DBModule.java
|   |-- LocalModule.java
|   |-- NetworkModule.java
|   `-- ThirdpartyService.java
|-- main
|   |-- MainActivity.java
|   `-- MainActivityComponent.java
|-- models
|   `-- WeatherData.java

网络模块,常用的httpclient,gson都在这里提供

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
@Module
public class NetworkModule {
@Singleton
@Provides
public OkHttpClient provideOkHttpClient() {
return new OkHttpClient();
}

@Singleton
@Provides
public Gson provideGson() {
return new Gson();
}

@Singleton
@Provides
public Retrofit provideRetrofit(OkHttpClient okHttpClient, Gson gson) {
return new Retrofit.Builder().baseUrl("http://api.openweathermap.org/data/2.5/")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();
}

@Singleton
@Provides
public ApiService provideApiService(Retrofit retrofit) {
return retrofit.create(ApiService.class);
}

@Singleton
@Provides
public ThirdpartyService provideThirdpartyService(Retrofit retrofit) {
return retrofit.create(ThirdpartyService.class);
}
}

Local模块,包括Cache和Shareperference,需要依赖Application

1
2
3
4
5
6
7
8
@Module
public class LocalModule {
@Provides
@Singleton
public Cache provideCache(Application application){
return new Cache(application.getCacheDir(),30*1024*1024);
}
}

BasicComponent基本管理类统一注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Singleton
@Component(modules = {AppModule.class, NetworkModule.class, LocalModule.class, DBModule.class})
public interface BasicComponent {
ApiService getApiService();

ThirdpartyService getThirdpartyService();

Gson getGson();

OkHttpClient getOkHttpClient();

Cache getCache();
}
//Application里调用
basicComponent = DaggerBasicComponent.builder()
.appModule(new AppModule(this))
.localModule(new LocalModule())
.networkModule(new NetworkModule())
.build();

同样的,此时需要给BasicComponent提供get方法,方便我们后面调用
好了,现在在Activity里就可以直接使用Application.getBasicComponent().getOkHttpClient()获取实例了,但是饶了这么大圈子,只是这样,跟直接写单例没什么区别。
我想直接在MainActivity里使用@Inject注入这些常用工具

  1. 新建ActivityScope
  2. MainActivityComponent里使用依赖BasicComponent模块
1
2
3
4
5
@ActivityScope
@Component(dependencies = BasicComponent.class)
public interface MainActivityComponent {
void inject(MainActivity activity);
}

MainActivity里只要@Inject指定需要的注入的工具就可以了

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
public class MainActivity extends AppCompatActivity{

@Inject
ApiService apiService;
@Inject
OkHttpClient okHttpClient;
@Inject
Gson gson;
@Inject
Cache cache;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

DaggerMainActivityComponent.builder().basicComponent(((HeroApplication) getApplication()).getBasicComponent()).build().inject(this);
}
@Override
protected void onResume() {
super.onResume();
//ApiService.getWeather()...
//OkHttpClient...
//gson...
}
}

上面的依赖,生成的代码里需要传入BasicComponent,实际上就是从basicComponent里取出来对MainActivity的@Inject成员变量赋值。因为我们在Application里已经初始化了BasicComponent,所以这里直接取,这里就可以说明@Singleton并没有
为我们做什么,生命周期是由我们自己控制的。为什么这里dependencies可以用,因为ActivityScope和Singleton名字不一样,仅此而已。所以说,层级关系,是逻辑上的,实际上的控制需要我们自己实现。
为什么在BasicComponent里我们需要暴露一些方法返回OkHttpClient等等?

This is actually an important property of how components work in Dagger: they do not expose types from their modules unless you explicitly make them available

Subcomponent

@Subcomponent同样可以实现上面的效果,@Subcomponentdependencies的区别有点像继承和组合,Subcomponent可以使用父Component的全部module资源,不需要在父Component里声明一些方法(像getOkHttpClient()这种)
插入一段文章:
The main difference between them is an objects graph sharing.
Subcomponents have access to entire objects graph from their parents while Component dependency gives access only to those which are exposed in Component interface.

以Activity和Fragment的关系为例

.
|-- HeroApplication.java
|-- common
|   `-- ActivityScope.java
`-- subcomponentdemo
    |-- ChildFragment.java
    |-- ChildFragmentComponent.java
    |-- SecondActivity.java
    |-- SecondActivityComponent.java
    `-- SecondActivityModule.java

用法比较奇怪,@Subcomponent修饰的类,需要在他的父Component上添加方法返回Subcomponent

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
@Module
public class SecondActivityModule {
@Provides
@ActivityScope
public Random provideRandom() {
return new Random();
}
}
//------------------
@ActivityScope
@Component(modules = {SecondActivityModule.class})
public interface SecondActivityComponent {
//如果是用@Subcomponent需要如下声明
ChildFragmentComponent simpleComponent();
}
//--------------
public class SecondActivity extends AppCompatActivity {
//生命周期自己把控
private SecondActivityComponent secondActivityComponent;


@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.second_activity);
secondActivityComponent = DaggerSecondActivityComponent.builder().build();
getSupportFragmentManager().beginTransaction().replace(R.id.main, new ChildFragment()).commit();
}

public SecondActivityComponent getSecondActivityComponent() {
return secondActivityComponent;
}
}

同样的还是得在Activity里添加Component的get方法,在ChildFragment里@Inject注入就可以了,但是Dagger2不会生成DaggerChildFragmentComponent这样的类,ChildFragmentComponent需要从附着的Activity上的SecondActivityComponent身上获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Subcomponent
//享用父级别的全部module
public interface ChildFragmentComponent {
void inject(ChildFragment fragment);
}
public class ChildFragment extends Fragment {
@Inject
Random random;

@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
((SecondActivity) activity).getSecondActivityComponent().simpleComponent().inject(this);
Preconditions.checkNotNull(random);
Log.i("TAG", "random:" + random);
}

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.child_fragment, container, false);
}
}

Lazy和Provider

Lazy有点类似懒汉模式的加载,他将创建对象延迟到第一次调用 ,Provider每次get得到的值都是不一样的。
TODO: 后面有更高级的用法再补

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MainActivity extends AppCompatActivity {
@Inject
Lazy<PersonController> mPersonProvider;

@Inject
Provider<NuclearController> mNuclearProvider;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerMainActivityComponent.builder().mainModule(new MainModule(10000)).build().inject(this);
mPersonProvider.get().output();// I am a controller
NuclearController nuclearController1 = mNuclearProvider.get();
NuclearController nuclearController2 = mNuclearProvider.get();
Log.i("TAG",(nuclearController1==nuclearController2)+"");//false
}
}

参考链接

我把自己学习的结论写在这里,理解这个过程花了不少时间,建议学习的过程还是看看源码。

Making a Best Practice App #4 — Dagger 2
Dagger2从入门到放弃再到恍然大悟
Dependency injection with Dagger 2 - Custom scopes
Dependency Injection with Dagger 2

注意事项

  1. dagger2要注意声明的component生命周期问题,需要自己控制
  2. Component里定义的方法名,不要纠结inject名字,可以随意起。是否要注入(void inject(Activity activity);)取决于你是否在用的地方(Activity)使用@Inject标注了变量