浅析Dagger2的使用
Dagger是为Android和Java平台提供的一个完全静态的,在编译时进行依赖注入的框架,原来是由Square公司维护,现在由Google维护。
我们知道Dagger是一个依赖注入的框架,那么什么是依赖注入呢?
我们在activity中有可能会用到很多很多的类,这些类要在activity中进行实例化,这样就导致我们的activity非常依赖这么多的类,这样的程序耦合非常
严重,不便于维护和扩展,有什么办法可以不去依赖这些类呢,这时候就需要有一个容器(IoC),将这些类放到这个容器里并实例化,我们activity在用
到的时候去容器里面取就可以了,我们从依赖类到依赖这个容器,实现了解耦,这就是我所理解的依赖注入,即所谓控制反转;
简单的说 Dagger就是用来创造这个容器,所有需要被依赖的对象在Dagger的容器中实例化,并通过Dagger注入到合适的地方,实现解耦,MVP框架就是为解耦而生,因此MVP和Dagger是绝配;
举个例子?
通常情况下我们引用一个类的做法:
我们先定义一个简单的类:
1 public class User { 2 private String name; 3 4 public String getName() { 5 return name; 6 } 7 8 public void setName(String name) { 9 this.name = name; 10 } 11 }
在Activity中对其操作
1 private void initData() { 2 3 User user = new User(); 4 5 user.setName("测试"); 6 }
以上是最普通的用法
接下来我们来看Dagger2的用法
我们先来配置一下Dagger2
首先在项目的 build.gradle:
1 dependencies { 2 classpath 'com.android.tools.build:gradle:1.5.0' 3 classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' 4 classpath 'me.tatarka:gradle-retrolambda:3.2.0' 5 // NOTE: Do not place your application dependencies here; they belong 6 // in the individual module build.gradle files 7 }
然后是APP的 build.gradle
1 apply plugin: 'com.android.application' 2 apply plugin: 'com.neenbedankt.android-apt' 3 apply plugin: 'me.tatarka.retrolambda' 4 android { 5 compileSdkVersion 23 6 buildToolsVersion "23.0.1" 7 8 defaultConfig { 9 applicationId "jiao.com.jiaoproject" 10 minSdkVersion 15 11 targetSdkVersion 23 12 versionCode 1 13 versionName "1.0" 14 } 15 buildTypes { 16 release { 17 minifyEnabled false 18 proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 } 20 } 21 22 compileOptions { 23 sourceCompatibility JavaVersion.VERSION_1_8 24 } 25 26 } 27 28 dependencies { 29 compile fileTree(dir: 'libs', include: ['*.jar']) 30 testCompile 'junit:junit:4.12' 31 compile 'com.android.support:appcompat-v7:23.3.0' 32 compile 'com.android.support:design:23.3.0' 33 apt 'com.google.dagger:dagger-compiler:2.2' 34 provided 'org.glassfish:javax.annotation:10.0-b28' 35 compile 'com.google.dagger:dagger:2.2' 36 compile 'com.jakewharton:butterknife:7.0.1' 37 compile 'com.squareup.okhttp3:logging-interceptor:3.3.0' 38 }
首先
1 public class User { 2 private String name; 3 4 @Inject 5 public User() { 6 } 7 8 public String getName() { 9 return name; 10 } 11 12 public void setName(String name) { 13 this.name = name; 14 } 15 }
发现有什么变化了没?@Inject是什么东东?待会我们来说;
接着我们看怎么使用
1 @Inject 2 User user; 3 4 private void initData() { 5 6 user.setName("测试"); 7 }
这时候我们允许程序发现空指针了;因为还缺少一个东西;
1 @Component 2 public interface ActivityComponent { 3 4 void inject(MainActivity MainActivity); 5 }
加上这个类之后 并且在Activity中对其初始化 完整代码如下:
1 @Inject 2 User user; 3 4 private void initData() { 5 DaggerActivityComponent.builder().build().inject(this); 6 user.setName("测试"); 7 }
这时候发现我们的user对象可以正常使用了;看上去感觉挺复杂的,但是对于大型项目引用的类过多的时候,Dagger的优势就体现出来了;
接下来我一一解答你们的疑惑;
首先我们来了解这几个基础概念:
- @Inject Inject主要有两个作用,一个是使用在构造函数上,通过标记构造函数让Dagger2来使用(Dagger2通过Inject标记可以在需要这个类实 例的时候来找到这个构造函数并把相关实例new出来)从而提供依赖,另一个作用就是标记在需要依赖的变量让Dagger2为其提供依赖。
- @Provide 用Provide来标注一个方法,该方法可以在需要提供依赖时被调用,从而把预先提供好的对象当做依赖给标注了@Injection的变量赋值。provide主要用于标注Module里的方法
- @Module 用Module标注的类是专门用来提供依赖的。有的人可能有些疑惑,看了上面的@Inject,需要在构造函数上标记才能提供依赖,那么如果我们需要提供 的类构造函数无法修改怎么办,比如一些jar包里的类,我们无法修改源码。这时候就需要使用Module了。Module可以给不能修改源码的类提供依 赖,当然,能用Inject标注的通过Module也可以提供依赖
- @Component Component一般用来标注接口,被标注了Component的接口在编译时会产生相应的类的实例来作为提供依赖方和需要依赖方之间的桥梁,把相关依赖注入到其中。
看了这些概念我们回到刚才的例子当中:
我们对User的构造函数进行了 @Inject的标注 意思就是告诉Dagger2 如果有谁要使用User这个类,我标注的这个构造函数,你可以直接用来实例化该类;
然后我们在Activity中对User也进行了@Inject的标注 意思是告诉Dagger2 这个类需要被注入,简单的说就是 这个类我要用,你帮我实例化;
细心的读者可能会发现 这样会不会太简单了,是的 是太简单了不太正常,哈哈,上面的例子中还有一个标注@Component 光靠@Inject的标注是不足以完成注入的 我们需要用@Component来完成注入;
上例中被@Component标记的ActivityComponent接口就是一个注入器; void inject(MainActivity MainActivity);的意思是MainActivity中要用到这个注入器然后我们在MainActivity中对注入器进行初始化 DaggerActivityComponent.builder().build().inject(this); 然后Activity中所有被@Inject标记的类,都会通过ActivityComponent来进行初始化;
我们再把上例中的注入过程梳理一下:
1、首先定义一个类User 并在其构造函数用@Inject标注,表示告诉Dagger2这是我的构造函数,如果有地方要用到我,就用该构造函数对我实例化;
2、创建一个@Component标注的注入器接口,并在注入器中使用 void inject(MainActivity MainActivity);来表明哪里要用到注入器;
这里表示MainActivity中要用到该注入器
3、在MainActivity中对注入器进行初始化DaggerActivityComponent.builder().build().inject(this); 初始化后该注入器就可以正常使用了;
4、在MainActivity中对需要注入的类 User用@Inject进行标注,表示该类需要被注入,即实例化;
注意:在代码编写过程中 我们会发现DaggerActivityComponent会不存在,这是因为注入器是在编译的过程中才生成,所以我们在对注入器编写完成后
Make Project 一下就会生成DaggerActivityComponent
————————————————————————————————————————————————————————————————————————————————
现在我们已经明白了@InJect @Component的作用了,接下来我们来研究@Module和@Provide
通过上面的例子我们发现 @Inject是对类的构造函数进行标注来进行实例化的,但是有些类,比如第三方OkHttpClient,我们是无法对其源码进行修改的
即对其构造函数进行标注,这个时候我们就用到了@Module
@Module是什么意思呢 @Module是和@Component配合使用的 意思就是告诉注入器,如果你在实例化对象的时候,没有找到合适的构造函数,你就来我这里找,@Module通常标注一个类,该类里面可以实例化各种类,Component在注入对象的时候先去Module中找,如果找不到就会检查所有被@Inject标注的构造函数;所以我们可以把OkHttpClient放到Module中;
1 @Module 2 public class ActivityMoudle { 3 4 @Provides 5 @Singleton 6 OkHttpClient provideOkHttpClient() { 7 HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(); 8 loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); 9 10 Interceptor apikey = chain -> chain.proceed(chain.request().newBuilder() 11 .addHeader("apikey", Constants.Api_Key).build()); 12 13 OkHttpClient okHttpClient = new OkHttpClient.Builder() 14 .readTimeout(Constants.HTTP_CONNECT_TIMEOUT, TimeUnit.MILLISECONDS) 15 .connectTimeout(Constants.HTTP_CONNECT_TIMEOUT, TimeUnit.MILLISECONDS) 16 .addInterceptor(apikey) 17 .addInterceptor(loggingInterceptor) 18 .build(); 19 20 return okHttpClient; 21 } 22 23 }
以上代码我们不需要知道是干嘛的,我们只知道该类中的方法返回一个okHttpClient的实例;
一个被@Module标注的类用来返回一个okHttpClient的实例;
我们再来看一下在Component中的代码:
1 @Singleton 2 @Component(modules = ActivityMoudle.class) 3 public interface ActivityComponent { 4 5 void inject(MainActivity MainActivity); 6 }
可以看到标注头多了@Component(modules = ActivityMoudle.class),表示告诉注入器如果你要注入的类没有找到构造函数,你就去ActivityMoudle.class中找
@Provide 用来标注一个方法,告诉注入器,我标注的方法你可以用来提供实例;
@Singleton 顾名思义,标注该实例化的对象为单例
然后我们在Activity直接标注使用就可以了
1 @Inject 2 OkHttpClient okHttpClient;
至此我们有两种方式可以提供依赖,一个是注解了@Inject的构造方法,一个是在Module里提供的依赖,那么Dagger2是怎么选择依赖提供的呢,规则是这样的:
- 步骤1:查找Module中是否存在创建该类的方法。
- 步骤2:若存在创建类方法,查看该方法是否存在参数
- 步骤2.1:若存在参数,则按从步骤1开始依次初始化每个参数
- 步骤2.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束
-
步骤3:若不存在创建类方法,则查找Inject注解的构造函数,看构造函数是否存在参数
-
步骤3.1:若存在参数,则从步骤1开始依次初始化每个参数
- 步骤3.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束