Dagger2初探(一)

参考:
Dagger2系统学习
Dagger2 入门,以初学者角度.
dagger 2 详解

在开始之前先扯会儿淡记录一下,最近又换了个公司(上个公司裁人),不过嘛,也不怎么样。所以今年必须要好好加油,努力在限定时间内(最多一年)进入更优秀的公司。说实话,人在全力投入的时候,就算很短的时间,也能有飞快的进步。
扯淡结束,谈谈为什么要学习dagger2.因为公司从外包那里买了代码,要我们在它的基础上继续做二次开发。我原以为外包的代码质量会很差,但是当我一打开他们的代码,发现他们的技术选型都是最新的,mvp + retrofit + rxjava2 + dagger2, 着实惊讶了一阵。不过苦恼也来了,就是dagger2是我以前从来没接触过的框架,据说上手难度还不小,有很多人就直接从入门到放弃了。但是对于我来说,有了梦想,还会害怕小小的困难?而且在高质量的别人的代码上继续开发正是我求之不得呢,难道在又老又旧的代码上继续开发更好?

添加依赖

dagger2在github上的地址是这个:
https://github.com/google/dagger
新建一个项目,我们按照地址中的说明添加依赖

1
2
3
4
dependencies {
compile 'com.google.dagger:dagger:2.16'
annotationProcessor 'com.google.dagger:dagger-compiler:2.16'
}

因为最新版本是2.16,所以就添加2.16的版本了。
看到后面还有一个叫dagger.android的库可以添加,但是我们这里暂时不添加,那个是dagger2专门针对Android的库,在后续的文章中再介绍。

@Inject

先来说@Inject这个注解。
首先创建一个User的实体类, 然后在构造方法上添加@Inject注解

1
2
3
4
5
6
7
8
9
10
11
public class User {

public String name;
public int age;

@Inject
public User() {
name = "mundane";
age = 10;
}
}

Activity中依赖了mUser这个成员变量,给mUser添加@Inject注解

1
2
3
4
5
6
7
public class MainActivity extends AppCompatActivity {

@Inject
User mUser;

...
}

然后创建Component类。
创建MainComponent类,专门对应于MainActivity

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

然后选择菜单build->Make Project,可以看到在build/generated/source/apt/debug/包名/目录下有一些文件生成

然后这个DaggerMainComponent就可以为我们所用了。

1
2
3
4
5
6
7
8
9
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerMainComponent.builder().build().inject(this);
mTv = findViewById(R.id.tv);
Log.d(TAG, "mUser = " + mUser);
mTv.setText("mUser = " + mUser);
}

打印结果:

1
05-11 16:19:54.556 25822-25822/me.mundane.dagger2learning D/MainActivity: mUser = me.mundane.dagger2learning.bean.User@65fb450

可以看到这个mUser对象已经不再为null了,那么它是什么时候被赋值的呢?答案就在DaggerMainComponent.builder().build().inject(this);这句代码里。

DaggerMainComponent.java

1
2
3
4
5
6
7
8
9
@Override
public void inject(MainActivity activity) {
injectMainActivity(activity);
}

private MainActivity injectMainActivity(MainActivity instance) {
MainActivity_MembersInjector.injectMUser(instance, new User());
return instance;
}

MainActivity_MembersInjector.java

1
2
3
public static void injectMUser(MainActivity instance, User mUser) {
instance.mUser = mUser;
}

另外值得注意的一点是,使用@Inject时,不能用private修饰类的成员变量,否则就会报如下的错误,注意红色箭头的地方:“Dagger does not support injection into private fields”

component的另一种写法

刚才我们看到component是这样一种写法:

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

但其实也可以这样,我们新建一个MainComponent2的类

1
2
3
4
@Component
public interface MainComponent2 {
User getUser();
}

然后改写MainActivity中注入代码的方式

1
2
3
4
5
6
7
8
9
10
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//DaggerMainComponent.builder().build().inject(this);
mUser = DaggerMainComponent2.builder().build().getUser();
mTv = findViewById(R.id.tv);
Log.d(TAG, "mUser = " + mUser);
mTv.setText("mUser = " + mUser);
}

打印的结果:

1
05-11 16:31:59.028 26504-26504/me.mundane.dagger2learning D/MainActivity: mUser = me.mundane.dagger2learning.bean.User@e352b4e

可以看到User对象也被成功注入了。
但是一般来说,我们都使用第一种写法,避免自己一个一个手动赋值。

@Module和@Provides

之前我们能成功注入一个User对象是因为我们在User的构造方法上添加了@Inject的注解,现在我们想注入一个textView对象,很明显我们无法做到在textView的构造方法上添加@Inject的注解(无法修改Android的系统源码),所以就有了@Module和@Provides这一种方式。

首先新建一个TextViewModule的类

1
2
3
4
5
6
7
8
@Module
public class TextViewModule {
@Provides
TextView provideTextView(Context context) {
return new TextView(context);
}
...
}

provideTextView方法需要一个Context参数,这个context我们从外部传入,然后添加一个provideContext的方法, TextViewModule的完整代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Module
public class TextViewModule {

private Context mContext;

public TextViewModule(Context context) {
mContext = context;
}

@Provides
TextView provideTextView(Context context) {
return new TextView(context);
}

@Provides
Context provideContext() {
return mContext;
}
}

接着我们还要修改Compoment和Activity中的代码。

首先修改MainComponent:

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

然后再次Make Project(记得每次新增或者修改Component或者新增@Inject的对象之后Make Project

然后,修改Activity中的代码,传入一个component的构建过程中传入一个textViewModule对象,完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class MainActivity extends AppCompatActivity {

private static final String TAG = "MainActivity";

@Inject
User mUser;

@Inject
TextView mTv;
private RelativeLayout mRl;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRl = findViewById(R.id.rl);
DaggerMainComponent.builder().textViewModule(new TextViewModule(this)).build().inject(this);
Log.d(TAG, "mUser = " + mUser);
Log.d(TAG, "mTv = " + mTv);
mTv.setText("mUser = " + mUser);
mRl.addView(mTv);
}
}

打印结果:

1
05-11 17:36:54.640 30446-30446/me.mundane.dagger2learning D/MainActivity: mTv = android.widget.TextView{5905649 V.ED..... ......ID 0,0-0,0}

可见这个textView对象已经被成功注入了。
由此可见,Dagger2框架调用provideTextView方法获取一个TextView实例时,发现要传一个Context类型的参数,这时候它会查找被@Provides注解并且返回值为Context类型的方法获取一个Context实例传入provideTextView方法,如果找不到,则会查找Inject注解的构造函数,看构造函数是否存在参数。

现在再来看,我们还可以把TextViewModule简化一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Module
public class TextViewModule {

private Context mContext;

public TextViewModule(Context context) {
mContext = context;
}

@Provides
TextView provideTextView() {
return new TextView(mContext);
}

}

两种写法都可以。

@Singleton

@Singleton是@Scope中的一种,@Scope表示注入对象的作用域或者说生命周期。@Singleton的字面含义是单例,但是它实际上并没有这么简单。我们还是实际来使用一下看看这个@Singleton的效果吧。
新建一个HttpModule类来提供OkhttpClient的实例

1
2
3
4
5
6
7
8
9
@Module
public class HttpModule {

@Singleton
@Provides
public OkHttpClient provideSingletonOkhttpClient() {
return new OkHttpClient.Builder().build();
}
}

需要在提供OkhttpClient实例的这个方法上添加@Singleton注解。

然后新建一个SingletonComponent

1
2
3
4
5
@Singleton
@Component(modules = HttpModule.class)
public interface SingletonComponent {
void inject(SingletonActivity activity);
}

module的provide方法使用了@Singleton或者别的Scope ,那么 component也必须使用@Singleton或者相同的Scope

然后在Activity中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class SingletonActivity extends AppCompatActivity {
private static final String TAG = "SingletonActivity";

@Inject
OkHttpClient mOkHttpClient1;

@Inject
OkHttpClient mOkHttpClient2;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_singleton);
DaggerSingletonComponent.create().inject(this);
Log.d(TAG, "mOkHttpClient1 = " + mOkHttpClient1);
Log.d(TAG, "mOkHttpClient2 = " + mOkHttpClient2);
}
}

打印的结果:

1
2
05-14 10:03:49.701 29536-29536/me.mundane.dagger2learning D/SingletonActivity: mOkHttpClient1 = okhttp3.OkHttpClient@bc66074
05-14 10:03:49.702 29536-29536/me.mundane.dagger2learning D/SingletonActivity: mOkHttpClient2 = okhttp3.OkHttpClient@bc66074

可以发现,这两个实例是一样的,这就是@Singleton所表示的单例的意思了。

但是事情并不是这么简单。
现在我们再新建一个页面,叫做SingletonActivity2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class SingletonActivity2 extends AppCompatActivity {
private static final String TAG = "SingletonActivity2";

@Inject
OkHttpClient mOkHttpClient1;

@Inject
OkHttpClient mOkHttpClient2;


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

DaggerSingletonComponent2.create().inject(this);

Log.d(TAG, "mOkHttpClient1 = " + mOkHttpClient1);
Log.d(TAG, "mOkHttpClient2 = " + mOkHttpClient2);
}
}

再新建一个SingletonComponent2,代码如下:

1
2
3
4
5
@Singleton
@Component(modules = HttpModule.class)
public interface SingletonComponent2 {
void inject(SingletonActivity2 activity);
}

依然是HttpModule提供依赖

然后我们修改SingltonActivity中的代码,提供一个按钮,点击跳转到SingletonActivity2

1
2
3
public void go2SingletonActivity2(View view) {
startActivity(new Intent(this, SingletonActivity2.class));
}

接着我们看看这两个Activity中的打印结果

1
2
3
4
5
05-14 10:58:04.956 9029-9029/me.mundane.dagger2learning D/SingletonActivity: mOkHttpClient1 = okhttp3.OkHttpClient@c043299
05-14 10:58:04.957 9029-9029/me.mundane.dagger2learning D/SingletonActivity: mOkHttpClient2 = okhttp3.OkHttpClient@c043299

05-14 10:58:09.909 9029-9029/me.mundane.dagger2learning D/SingletonActivity2: mOkHttpClient1 = okhttp3.OkHttpClient@990982f
05-14 10:58:09.909 9029-9029/me.mundane.dagger2learning D/SingletonActivity2: mOkHttpClient2 = okhttp3.OkHttpClient@990982f

可以发现,在同一个页面中,是两个相同的实例,但是在不同的页面中,是不同的实例。

解释:@Singleton的生命周期依附于Component,不同的Component就算关联了相同的Module,创建的实例也不同。

自定义@Scope和dependencies

上面我们说到,在一个项目中,多个页面的okhttpClient不是同一个实例,而如果我们希望提供的OkHttpClient是一个实例,该怎么办呢?

首先,创建一个Module,提供OkhttpClient

1
2
3
4
5
6
7
8
9
@Module
public class AppModule {

@Singleton
@Provides
public OkHttpClient provideSingletonOkhttpClient() {
return new OkHttpClient().newBuilder().build();
}
}

然后创建一个全局的Component

1
2
3
4
5
@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {
OkHttpClient provideSingletonOkhttpClient();
}

module的provide 方法使用了 scope(这里使用了@Singleton),那么component就必须使用同一个注解因此在AppComponent上也加入了@Singleton注解。这里建立全局的目的很明确,就是提供了单例OkHttpClient。

然后就在其他的Component中引用全局的Component, 这里就需要用到@Component中的dependencies属性了。

SingletonComponent.java

1
2
3
4
5
@Singleton
@Component(modules = HttpModule.class, dependencies = AppComponent.class)
public interface SingletonComponent {
void inject(SingletonActivity activity);
}

SingletonComponent2.java

1
2
3
4
5
@Singleton
@Component(modules = HttpModule.class, dependencies = AppComponent.class)
public interface SingletonComponent2 {
void inject(SingletonActivity2 activity);
}

然后我们在HttpModule中不再需要提供创建OkHttpClient实例的 provide 方法了。我们把代码注释掉。

1
2
3
4
5
6
7
8
9
@Module
public class HttpModule {

//@Singleton
//@Provides
//public OkHttpClient provideSingletonOkhttpClient() {
// return new OkHttpClient.Builder().build();
//}
}

Make Project一下, 然后发现报错了

报错的原因说的是Singleton的组件不能依赖其他的scope的组件。其实这里就涉及到这样一条规律:Singleton的组件不能依赖其他的scope的组件,只能其他scope的组件依赖Singleton的组件。
所以我们将SingletonComponent和SingletonComponent2上面的@Singleton注解去掉,再次Make Project, 发现又报错了

报错原因说没有Scope注解的的组件不能依赖有Scope注解的组件。好吧,那我们参照一下@Singleton然后自己写一个自定义的Scoped注解吧。
首先打开@Singleton这个注解的源码:

1
2
3
4
5
6
7
8
9
/**
* Identifies a type that the injector only instantiates once. Not inherited.
*
* @see javax.inject.Scope @Scope
*/
@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}

可以发现@Singleton注解也是一个@Scope注解,所以我们自定义一个ActivityScope,表示这个Activity页面中的局部单例

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

然后我们修改HttpModule

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Module
public class HttpModule {

//@Singleton
//@Provides
//public OkHttpClient provideSingletonOkhttpClient() {
// return new OkHttpClient.Builder().build();
//}

@ActivityScope
@Provides
public Person providePerson() {
return new Person();
}
}

Person类是我自己自己定义的一个实体类,很简单的。

再修改SingletonComponent和SingletonComponent2

1
2
3
4
5
@ActivityScope
@Component(modules = HttpModule.class, dependencies = AppComponent.class)
public interface SingletonComponent {
void inject(SingletonActivity activity);
}
1
2
3
4
5
@ActivityScope
@Component(modules = HttpModule.class, dependencies = AppComponent.class)
public interface SingletonComponent2 {
void inject(SingletonActivity2 activity);
}

再修改SingletonActivity的代码

可以看到DaggerSingletonComponent中多了一个appComponent的方法,需要传入一个AppComponent的实例。SingletonComponent和SingletonComponent2既然依赖AppComponent,肯定需要你传入一个AppComponent实例,而且需要同一个AppComponent的实例,因为之前说过了, 不同的Component就算关联了相同的Module,创建的实例也不同。

所以我们将这个AppComponent实例的创建放在Application创建的时候,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyApplication extends Application {

private AppComponent mAppComponent;

@Override
public void onCreate() {
super.onCreate();
mAppComponent = DaggerAppComponent.create();
}

public AppComponent getAppComponent() {
return mAppComponent;
}
}

抽取一个BaseActivity

1
2
3
4
5
6
7
public class BaseActivity extends AppCompatActivity {

public AppComponent getAppComponent() {
MyApplication myApplication = (MyApplication) getApplication();
return myApplication.getAppComponent();
}
}

再修改SingletonActivity, SingletonActivity2的修改也是类似

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
public class SingletonActivity extends BaseActivity {
private static final String TAG = "SingletonActivity";

@Inject
OkHttpClient mOkHttpClient1;

@Inject
OkHttpClient mOkHttpClient2;

@Inject
Person mPerson1;

@Inject
Person mPerson2;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_singleton);
DaggerSingletonComponent.builder().appComponent(getAppComponent()).build().inject(this);
Log.d(TAG, "mOkHttpClient1 = " + mOkHttpClient1);
Log.d(TAG, "mOkHttpClient2 = " + mOkHttpClient2);
Log.d(TAG, "mPerson1 = " + mPerson1);
Log.d(TAG, "mPerson2 = " + mPerson2);
}

public void go2SingletonActivity2(View view) {
startActivity(new Intent(this, SingletonActivity2.class));
}
}

查看日志打印:
SingletonActivity:

1
2
3
4
05-14 17:27:33.463 9874-9874/me.mundane.dagger2learning D/SingletonActivity: mOkHttpClient1 = okhttp3.OkHttpClient@829f409
05-14 17:27:33.463 9874-9874/me.mundane.dagger2learning D/SingletonActivity: mOkHttpClient2 = okhttp3.OkHttpClient@829f409
05-14 17:27:33.463 9874-9874/me.mundane.dagger2learning D/SingletonActivity: mPerson1 = me.mundane.dagger2learning.bean.Person@a2dd60e
05-14 17:27:33.463 9874-9874/me.mundane.dagger2learning D/SingletonActivity: mPerson2 = me.mundane.dagger2learning.bean.Person@a2dd60e

SingletonActivity2:

1
2
3
4
05-14 17:27:40.882 9874-9874/me.mundane.dagger2learning D/SingletonActivity2: mOkHttpClient1 = okhttp3.OkHttpClient@829f409
05-14 17:27:40.883 9874-9874/me.mundane.dagger2learning D/SingletonActivity2: mOkHttpClient2 = okhttp3.OkHttpClient@829f409
05-14 17:27:40.883 9874-9874/me.mundane.dagger2learning D/SingletonActivity2: mPerson1 = me.mundane.dagger2learning.bean.Person@6837770
05-14 17:27:40.883 9874-9874/me.mundane.dagger2learning D/SingletonActivity2: mPerson2 = me.mundane.dagger2learning.bean.Person@6837770

可以发现,4个OkhttpClient实例都是同一个,说明确实是应用内的单例。
同一个页面中的Person实例是同一个,但是不同页面中的Person就不是同一个了,实现的是页面内的单例。

总结起来就是,Scope的生命周期依附于component,不同的component,创建的实例不同。

@SubComponent

@SubComponent如下:
首先写一个ChildComponent

1
2
3
4
5
@ActivityScope
@Subcomponent(modules = ChildModule.class)
public interface ChildComponent {
void inject(SubcomponentActivity activity);
}

看一下这个ChildModule

1
2
3
4
5
6
7
8
9
@Module
public class ChildModule {

@ActivityScope
@Provides
public OkhttpClientManager provideOkhttpClientManager(OkHttpClient okHttpClient){
return new OkhttpClientManager(okHttpClient);
}
}

可以看到这里要生成OkhttpClientManager的实例需要一个okHttpClient的实例,本应该在ChildMudule里面继续提供创建OkHttpClient实例的方法,但是这里就不一样了,他会主动到“父类”FatherComponent与之关联的OkhttpClientModule里面去找这个实例,那么怎么将父子组件进行关联呢?
我们看FatherComponent

1
2
3
4
5
6
@Singleton
@Component(modules = OkhttpClientModule.class)
public interface FatherComponent {
// 实现了父子组件之间的关联
ChildComponent getChildComponent();
}

这样就完成了父子组件之间的关联。
然后再Activity中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class SubcomponentActivity extends AppCompatActivity {

private static final String TAG = "SubcomponentActivity";

@Inject
OkhttpClientManager mOkhttpClientManager;

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

FatherComponent fatherComponent = DaggerFatherComponent.builder().build();

ChildComponent childComponent = fatherComponent.getChildComponent();

childComponent.inject(this);

Log.d(TAG, "mOkhttpClientManager = " + mOkhttpClientManager);


}
}

打印结果:

1
05-15 10:31:39.408 25998-25998/me.mundane.dagger2learning D/SubcomponentActivity: mOkhttpClientManager = me.mundane.dagger2learning.bean.OkhttpClientManager@66c6981

可以看到,成功注入了一个OkhttpClientManager对象。
简单总结一下@Subcomponent的使用:

  1. 子组件的声明方式为@Subcomponent
  2. 在父组件中要声明一个返回值为子组件的方法
  3. 上面第2条中的方法可以传递参数,当子组件需要什么Module时,就在该方法中添加该类型的参数,但是参数类型必须是子组件关联的modules中的一种,否则会报错

注意:

  1. 注意:用@Subcomponent注解声明的Component是无法单独使用的,想要获取该Component实例必须经过其父组件
  2. 父亲和孩子Component都具备Scope,但是父亲定义的Scope大于孩子定义的Scope。父亲FatherComponent 定义的Scope是@Singleton,而孩子SubComponent定义的Scope也是@Singleton,这样就会报错。那么修改孩子的Scope的级别,例如修改为自定义的@ActivityScope是可以的。

demo地址:

Dagger2Learning

0%