Dagger2 这次入门就不用放弃了
前言
之前也研究过很多次Dagger2
这东西了,可能以后RxJava+Retrofit+MVP+Dagger2
是Android
发展的主流框架,看了Dagger2
的实现代码,有点不明所以。上网也有很多文章介绍依赖注入、Dagger2
的组件等等那些,这样这样这样什么组件呀、模块呀、注入呀。但是感觉对于入门来说那些文章都没有说到点子上,具体怎么用这个核心点而且应该怎么写代码?为什么这样写,并没有很明确的说明。我来回看了几遍代码之后,总结出了一点经验,不知道说的对不对。
没有了解过Android MVP
结构的同学可能不利于阅读。
为什么使用Dagger2
对于这个问题我也困惑了很久,Java
代码就是这样写,并没有考虑过依赖注入是什么鬼,并且依赖注入有什么不好。这篇文章详细介绍了依赖注入,感兴趣的可以传送过去看看。
简单来说,依赖注入就是为了控制反转和解耦的,这些高深的名词儿可能一时也不懂。不要紧,我举个栗子就能明白了,请看代码:
class A{
}
class B{
A a;
public B(){
a = new A();
}
}
上面的代码很简单,class B
持有一个class A
的对象,然后假如根据业务需求需要修改A类的某些实现,这样的话就需要修改B类中的创建A对象的方式。假想一下,当你的代码规模达到一定的程度的时候,需要改一部分代码,牵一而发动全身,需要改的代码量多,而且容易出错。还有一个不好的情况就是,当要对A进行单元测试的时候,就要测试B,这样的耦合可能不是程序员希望看见的。Dagger2
就是为了解决这样的问题而出现的。这里只是一个简单的例子,可能描述依赖注入的原理不是很清晰,如果不是很了解的话可以从网上搜索出很多文章。
Dagger2
的配置
- 目录添加
apt
支持,apt
是用于自动生成代码来进行依赖注入的。
项目中的build.gradle
添加:
dependencies {
classpath ‘com.android.tools.build:gradle:2.1.0‘
classpath ‘com.neenbedankt.gradle.plugins:android-apt:1.4‘
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
- 在
module
的build.gradle
添加:
apply plugin: ‘com.neenbedankt.android-apt‘
android{
...
}
dependencies {
provided ‘org.glassfish:javax.annotation:10.0-b28‘
compile ‘com.google.dagger:dagger:2.5‘
compile ‘com.google.dagger:dagger-compiler:2.5‘
}
例子
这里通过一个例子来向Activity
注入一些成员变量。(例子代码来自网上),来说明Dagger2
的基本使用。
例子使用的是MVP
模式,内容是通过注入一个Presenter
,然后通过Presenter
来设置TextView
显示内容为user.name
;
其中User
的代码如下:
public class User {
public String name;
public User(String name) {
this.name = name;
}
}
Presenter
的代码:
public class DaggerPresenter {
DaggerActivity activity;
User user;
public DaggerPresenter(DaggerActivity activity, User user) {
this.user = user;
this.activity = activity;
}
public void showUserName() {
activity.showUserName(user.name);
}
}
现在的场景是有一个DaggerActivity
,里面持有一个DaggerPresenter
的成员,我们该如何使用Dagger2
来注入这个成员呢?
第一步:编写Module
我这里编写了一个ActivityModule
,代码如下:
@Module
public class ActivityModule {
private DaggerActivity activity;
public ActivityModule(DaggerActivity activity) {
this.activity = activity;
}
@Provides
public DaggerActivity provideActivity() {
return activity;
}
@Provides
public User provideUser() {
return new User("user form ActivityModule");
}
@Provides
public DaggerPresenter provideDaggerPresenter(DaggerActivity activity, User user) {
return new DaggerPresenter(activity, user);
}
}
首先这里编写有一些规则的,类需要用@Module
注解来标示,可以看到我这个AcitivtyModule
中定义了一个构造函数,需要传进来一个DaggerActivity
对象。
首先我们需要明确一个点,就是Module
的作用是用来提供生成依赖对象的,比如我要注入DaggerPresenter
,那么这个Module
的作用就是需要生成一个DaggerPresenter
的对象,来让Dagger2
注入到DaggerActivity
中。
所以我们这里需要编写一个函数provideDaggerPresenter
,这个函数可以从上面的代码看出,我们需要对这个函数使用@Provides
注解,然后,我们这里需要传入两个参数,一个DaggerActivity
,一个User
对象。那么,这两个参数从何而来呢?
细心的同学可能会发现,我上面的代码中还定义了两个函数,分别为provideUser
和provideActivity
,大家猜出点什么没有(嘿嘿),这里provideDaggerPresenter
的两个参数就是通过这两个函数来获取的。如果没有声明这两个函数的话,可能编译期间会报错哟。通过上述内容,各位同学应该明白了Module
应该如何编写了吧。
编写Module
有以下几个注意点:
- 类需要用
@Module
来标明注解 - 这里有一点规则,用
@Provides
注解的函数需要以provide
开头,然后后面接什么内容都可以,看自己喜欢,事实上,经过我的测试,我把provideActivity()
改成provideA()
同样是可以注入成功的,所以大家可以知道,这里是根据返回值类型来标识的,方法名并不重要,只需要保证以provide
开头即可。
第二步:编写ActivityComponent
请看代码:
@Component(modules = ActivityModule.class)
public interface ActivityComponent {
void inject(DaggerActivity daggerActivity);
}
这里的代码够少吧,哈哈,我们编写的这个Component
需要用@Component
注解来标识,同时声明了modules
为上面编写的ActivityComponent
,然后提供了一个方法,叫做inject
,用来在Activity
中注入。(这里为什么要写一个方法叫做inject
我暂时还没弄清楚,改名字是可以的,但是参数类型不能改,并且一定要指定module=ActivityModule
才能注入),这里我们暂且理解为提供一个方法来注入对象吧。
第三步:AndroidStudio -> Build -> Make Project
写到这里的时候就可以Make Project
了,完成之后apt会自动生成一个以Dagger
开头的Component
,比如,我们上面写的是ActivityComponent
,生成了类名就为DaggerActivityComponent
。这个类我们可以直接使用。
第四步,注入Activity中
在第三步中已经生成了一个DaggerActivityComponent
了,我们在Activity
的onCreated
函数中编写如下代码:
DaggerActivityComponent.builder()
.activityModule(new ActivityModule(this))
.build()
.inject(this);
可以看到我们首先调用这个了类的builder()
,然后调用一些方法。这些方法也有一些规律噢,比如我们的ActivityComponent
指定的module
是ActivityModule
,DaggerActivityComponent
就会有一个名为activityModule
的方法,我们需要调用它,并传入参数,这里我们直接new
了一个ActivityModule
进去。
好了,到此为止,我们已经使用Dagger2
形成了关联,我们还需要注入Presenter
。在Activity
中:
@Inject
DaggerPresenter presenter;
我们直接使用注解@Inject
就可以对这个成员进行注入了。
下面是我的Activity
的完整代码:
public class DaggerActivity extends AppCompatActivity {
private static final String TAG = "DaggerActivity";
TextView text;
@Inject
DaggerPresenter presenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_dagger);
text = (TextView) findViewById(R.id.text);
inject();
presenter.showUserName();
//Log.i(TAG, "client = " + (client == null ? "null" : client));
}
private void inject() {
DaggerActivityComponent.builder().activityModule(new ActivityModule(this))
.build().inject(this);
}
public void showUserName(String name) {
text.setText(name);
}
}
上面的代码运行起来的结果就是在DaggerActivity
的TextView
中显示了一串字符串"user form ActivityModule"
,虽然例子简单,但是基本上实现了简单依赖注入,希望对于Dagger2
的入门有点启发。
好啦,现在我们的项目又有新需求了,我们希望提供一个全局的OkHttpClient
和Retrofit
对象来进行网络请求,他的生命周期是和APP
一致的,这个时候我们就需要定制AppComponent
了。
首先我们按照老规矩,第一步先编写Module
,一下是ApiModule
:
@Module
public class ApiModule {
public static final String END_POINT = "http://www.baidu.com";
@Provides
@Singleton
OkHttpClient provideOkHttpClient() {
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(60 * 1000, TimeUnit.MILLISECONDS)
.readTimeout(60 * 1000, TimeUnit.MILLISECONDS)
.build();
return client;
}
@Provides
@Singleton
Retrofit provideRetrofit(OkHttpClient client) {
Retrofit retrofit = new Retrofit.Builder()
.client(client)
.baseUrl(END_POINT)
.build();
return retrofit;
}
@Provides
@Singleton
User provideUser(){
return new User("name form ApiProvide");
}
}
请注意,我这里的provide
方法额外添加了一个@SingleTon
注解,这里说明是全局单例的对象,而且我这里改动了一小部分代码,把ActivityModule
的provideUser
移动到这里来了,我这里是为了演示依赖过程。
接下来编写AppComponent
了:
@Singleton
@Component(modules = {ApiModule.class})
public interface AppComponent {
OkHttpClient getClient();
Retrofit getRetrofit();
User getUser();
}
这里的AppComponent
提供了3个方法,分别用来暴露OkHttpClient
、Retrofit
和User
对象的,这里暂且不提为什么要暴露,大家别急,继续往下看。
第三部就是Make Project
了,之后就会生成一个叫做DaggerAppComponent
的类,之后我们在MyApplicaiotn
中实例化这个Component
:
public class MyApplication extends Application {
AppComponent appComponent;
@Override
public void onCreate() {
super.onCreate();
appComponent = DaggerAppComponent.builder()
.apiModule(new ApiModule())
.build();
}
public AppComponent getAppComponent() {
return appComponent;
}
}
这里别忘了在AndroidManifest
中设置为自定义的MyApplicaiton
哦。上面的代码很简单,我们只是实例化了一个AppComponent
,然后提供了一个方法用于获取这个Component
。
然后我们需要修改一下ActivityComponent
,改成下面这样:
@ActivityScope
@Component(modules = ActivityModule.class,dependencies = AppComponent.class)
public interface ActivityComponent {
void inject(DaggerActivity daggerActivity);
}
改动的地方呢是添加了一个@ActivityScope
然后,添加了一个dependencies = AppComponent.class
。没错,Component
之间也可以依赖的。
解释一下这个ActivityScope
,这里查询了网上的资料之后,据说是可以和Activity
的生命周期绑定,没有声明这个注解的话编译会报异常。我暂时无法对这个Scope
理解清晰,不做评论。
@Scope
public @interface ActivityScope {
}
最后一步啦,改动DaggerActivity
:
public class DaggerActivity extends AppCompatActivity {
private static final String TAG = "DaggerActivity";
TextView text;
@Inject
DaggerPresenter presenter;
@Inject
OkHttpClient client;
@Inject
Retrofit retrofit;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_dagger);
text = (TextView) findViewById(R.id.text);
inject();
presenter.showUserName();
Log.i(TAG, "client = " + (client == null ? "null" : client));
Log.i(TAG, "retrofit = " + (retrofit == null ? "null" : retrofit));
}
private void inject() {
AppComponent appComponent = ((MyApplication) getApplication()).getAppComponent();
DaggerActivityComponent.builder()
.appComponent(appComponent)
.activityModule(new ActivityModule(this))
.build().inject(this);
}
public void showUserName(String name) {
text.setText(name);
}
}
可以看到我这里添加了两个注入,分别注入了一个OkHttpClient
和一个Retrofit
对象,然后在注入的时候也把AppComponent
也添加进来了。然后我们先看运行结果,后面我会解释一下整个依赖关系。
运行结果:
Log
输出:
07-13 12:24:46.433 12424-12424/com.sample I/DaggerActivity: client = [email protected]
07-13 12:24:46.433 12424-12424/com.sample I/DaggerActivity: retrofit = [email protected]
然后在手机上运行的话,TextView
会显示"name from ApiProvide"
,从结果看来我们已经成功注入了这3个对象。
现在估计大家有些疑问。
- 首先我们看回
ActivityComponent
:
@Module
public class ActivityModule {
private DaggerActivity activity;
public ActivityModule(DaggerActivity activity) {
this.activity = activity;
}
@Provides
public DaggerActivity provideActivity() {
return activity;
}
@Provides
public DaggerPresenter provideDaggerPresenter(DaggerActivity activity, User user) {
return new DaggerPresenter(activity, user);
}
}
这里的provideUser
方法已经去掉了,那么根据我前面说的话,那我们需要从哪里获取这个User
对象呢。我们看回前面的:
@ActivityScope
@Component(modules = ActivityModule.class,dependencies = AppComponent.class)
public interface ActivityComponent {
void inject(DaggerActivity daggerActivity);
}
可以看到这个ActivityComponent
是依赖AppComponent
的,AppComponent
中定义了3个方法:
OkHttpClient getClient();
Retrofit getRetrofit();
User getUser();
分别用来提供这三个对象的,这样就可以解释清楚了,他们存在依赖关系,就像我们对象之间的继承一样,值得注意的是这三个方法也是根据返回值类型来识别的,他们会分别找到AppComponent
中的module(ApiModule)
中的provide
方法来获取对象。
这里我们发现一个有趣的现象,首先我们提供这三个方法可以被Activity
的成员变量注入(可以看到,我们成功的注入的OkHttpClient
和Retrofit
),同时也可以让被依赖的Component(ActivityComponent)
所使用.
如果我们不把这三个对象声明在AppComponent
中,在编译的过程中就会报异常。在专业术语好像叫做:暴露给子图?
结论
我这里只是对于怎么使用Dagger2
来了一个流程,并且做出了一些通俗化的解释。听到很多人说这个Dagger2
入门困难,可能是因为需要理解完Dagger2
通过APT
生成的代码的流程才能完全理解吧。但是我们通常学习一个框架是学会怎么使用,使用过了之后,才会对它的原理进行了解,然而Dagger2
的使用起来也并不简单,对于一个没有接触过Dagger1
,又没有了解过依赖注入的概念的人来说,一下子需要看明白还是有点难度的。我也是经历了很多次入门到放弃,感觉自己现在也是理解的不太清晰,其实都是猜的(嘿嘿)。总之这篇文章的着重点是为了让大家知道如何使用Dagger2
,并没有解释过内部的原理,但是希望这些东西能带给一些想入门Dagger2
又感觉难以理解的人一点点启发吧。
Demo源码:https://github.com/Jamlh/Sample/tree/master/app/src/main/java/com/sample/dagger