Dagger2使用详解
什么是依赖注入?
依赖注入就是将调用者依赖的对象实例通过一定的方式从外部传入,解决了各个类之间的耦合问题。
这个外部,正是dagger2容器。需要什么对象从容器中取就行了,调用和被调用方被隔离开,通过一个容器将他们联系起来,从而实现了解耦。
Dagger2是Google出的依赖注入框架。Dagger2的原理是在编译期生成相应的依赖注入代码。其他框架是在运行时期反射获取注解内容,影响运行效率。
引入Dagger2
在app的build.grade中引入android-apt,并添加dagger2依赖
apply plugin: 'com.neenbedankt.android-apt'
dependencies {
apt "com.google.dagger:dagger-compiler:2.11-rc1"
provided 'org.glassfish:javax.annotation:10.0-b28'
compile "com.google.dagger:dagger:2.11-rc1"
}
在project级别的build.grade中添加android-apt
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.2'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
Dagger2基本用法
@Inject
通常在需要依赖的地方使用这个注解,换句话说,你用它告诉dagger2这个类或这个字断需要依赖注入,这样dagger2就会构造一个这个类的实例去满足他们的依赖
@Module
Module类里面的方法专门提供依赖,所以我们定义一个类,用@Module注解,这样dagger2在构造类实例的时候就知道去哪里找这些依赖。
@Provide
在Module中,我们定义的方法使用这个注解,告诉dagger2我们想要构造对象并提供这些依赖
@Component
Component是一个注入器,也可以说是连接Inject和Module的桥梁
下面看个例子,通过例子来看下dagger2的基本用法:
// MainActivity.java
public class MainActivity extends AppCompatActivity {
@Inject LoginManager loginManager;
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerLoginModuleComponent.create().inject(this);
loginManager.login();
}
}
调用LoginManager的login接口,因此需要将LoginManager对象注入到MainActivity中,注入的操作由DaggerLoginModuleComponent调用inject方法完成的,LoginModuleComponent是怎么和LoginManager关联上的呢?
// LoginModuleComponent.java
@Component(modules = {LoginModule.class})
public interface LoginModuleComponent {
void inject(MainActivity activity);
}
事实上,LoginModuleComponent只是LoginModule和MainActivity的连接器,可以想象成注射器,把LoginModule提供的对象注射到MainActivity中,完成依赖注入的过程。再看下LoginModule中提供了哪些对象?
// LoginModule.java
@Module
public class LoginModule {
@Provides
public LoginManager providerLoginManager() {
return new LoginManager();
}
}
// LoginManager.java
public class LoginManager {
public void login() {
Log.i("dagger2", "--- login ---");
}
}
可以看到是通过Provides注解来提供LoginManager对象的,用起来还是很简单的,运行一下:
04-27 18:38:05.893 2343-2343/com.testdes.des I/dagger2: --- login ---
Dagger2模块化
LoginManager中执行login操作,需要OkHttpClient对象,我们从构造方法中传入,如下:
// LoginManager.java
public class LoginManager {
OkHttpClient client;
public LoginManager(OkHttpClient client) {
this.client = client;
}
public void login() {
Log.i("dagger2", "--- login:" + client);
}
}
// LoginModule.java
@Module
public class LoginModule {
@Provides
public LoginManager providerLoginMgr(OkHttpClient client) {
Log.i("dagger2", "--providerLoginMgr");
return new LoginManager(client);
}
@Provides
public OkHttpClient okHttpClient() {
return new OkHttpClient();
}
}
考虑到OkHttpClient不仅仅在登录的时候使用,其它网络操作也需要这个对象,因此把OkHttpClient做成公共模块,通过HttpModule提供出来。
// HttpModule.java
@Module
public class HttpModule {
@Provides
public OkHttpClient providerOKHTTP() {
return new OkHttpClient();
}
}
// LoginModule.java
@Module(includes = {HttpModule.class})
public class LoginModule {
@Provides
public LoginManager providerLoginMgr(OkHttpClient client) {
Log.i("dagger2", "--providerLoginMgr");
return new LoginManager(client);
}
}
在LoginModule中通过includes属性把HttpModule引入进来就可以了,实现了简单的HttpModule模块化,当然还有其他的方式,比如在LoginModuleComponent中引入HttpModule
@Component(modules = {LoginModule.class, HttpModule.class})
public interface LoginModuleComponent {
void inject(MainActivity activity);
}
或者创建HttpComponent,通过dependencies依赖,如下:
@Component(modules = {LoginModule.class}, dependencies = HttpComponent.class)
public interface LoginModuleComponent {
void inject(MainActivity activity);
}
Dagger2单例
单例只需要使用@Singleton注解来声明,当HttpModule的providerOKHTTP使用了Singleton,LoginModuleComponent也需要使用Singleton来标注,如下:
// HttpModule.java
@Module
public class HttpModule {
@Provides
public OkHttpClient providerOKHTTP() {
return new OkHttpClient();
}
}
// LoginModuleComponent.java
@Component(modules = {HttpModule.class})
@Singleton
public interface LoginModuleComponent {
void inject(MainActivity activity);
}
// MainActivity.java
public class MainActivity extends AppCompatActivity {
@Inject OkHttpClient okHttpClient;
@Inject OkHttpClient okHttpClient2;
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerLoginModuleComponent.create().inject(this);
Log.i("dagger2", ""+okHttpClient);
Log.i("dagger2", ""+okHttpClient2);
}
}
运行一下,确实是单例
04-28 14:47:03.553 8497-8497/com.testdes.des I/dagger2: com.testdes.des.dagger2.OkHttpClient@3ef1ff
04-28 14:47:03.553 8497-8497/com.testdes.des I/dagger2: com.testdes.des.dagger2.OkHttpClient@3ef1ff
我们在增加一个页面SettingActivity,假设有个登出的功能,也需要用到OkHttpClient,代码不贴了,运行一下:
04-28 15:29:47.999 9781-9781/com.testdes.des I/dagger2: MainActivity:com.testdes.des.dagger2.OkHttpClient@3ef1ff
04-28 15:29:48.068 9781-9781/com.testdes.des I/dagger2: SettingActivity:com.testdes.des.dagger2.OkHttpClient@ba0d601
哇擦,又发现不单例了!!这是什么鬼?
这里解释下,dagger2的单例和传统java单例不一样,我们通常说的单例是在静态域里初始化的,只要程序不退出就一直保持单例,而dagger2单例是依附于component,即使声明了singleton,不同的component也不是单例,参考文章末尾的注意事项第7条。
所以我们如果要实现单例,需要怎么做呢?
思路是定义一个HttpComponent,作为其他component的dependencies component,具体如下:
// HttpComponent.java
@Component(modules = {HttpModule.class})
@Singleton
public interface HttpComponent {
}
// LoginModuleComponent.java
@Component(dependencies = {HttpComponent.class})
@Singleton
public interface LoginModuleComponent {
void inject(MainActivity activity);
}
运行一下,发现报错了
Error:(11, 1) error: This @Singleton component cannot depend on scoped components:
@Singleton com.testdes.des.dagger2.HttpComponent
错误原因参考文章末尾注意事项第4条,Singleton是一个scope, LoginModuleComponent和HttpComponent的scope不能相同,那我们只好给LoginModuleComponent自定义一个scope了,怎么定义可以参考Singleton
// ActivityScope.java
@Scope
@Documented
@Retention(RUNTIME)
public @interface ActivityScope {
}
// LoginModuleComponent.java
@Component(dependencies = {HttpComponent.class})
@ActivityScope
public interface LoginModuleComponent {
void inject(MainActivity activity);
}
运行一下,终于还是报错了
Error:(13, 8) error: com.testdes.des.dagger2.OkHttpClient cannot be provided without an @Inject constructor or from an @Provides- or @Produces-annotated method.
com.testdes.des.dagger2.OkHttpClient is injected at
com.testdes.des.MainActivity.okHttpClient
com.testdes.des.MainActivity is injected at
com.testdes.des.dagger2.LoginModuleComponent.inject(activity)
LoginModuleComponent没有办法得到HttpComponent提供的OkHttpClient对象,这是需要做一个桥接,增加一个get方法(名字任意取),返回一个OkHttpClient对象,如下
@Component(modules = {HttpModule.class})
@Singleton
public interface HttpComponent {
OkHttpClient get();
}
BUILD SUCCESSFUL
在MainActivity中注入的方法修改下:
// MainActivity.java
DaggerLoginModuleComponent.builder()
.httpComponent(DaggerHttpComponent.create()).build().inject(this);
需要构建一个httpComponent对象,按照上面的写法,每次用到都要调用DaggerHttpComponent.create()显然不合理,因此我们把httpComponent构建操作放到Application初始化的时候执行一次,如下:
// TestApplication.java
public class TestApplication extends Application{
HttpComponent httpComponent;
@Override public void onCreate() {
super.onCreate();
System.out.println("start TestApplication");
httpComponent = DaggerHttpComponent.create();
}
public HttpComponent getHttpComponent() {
return httpComponent;
}
}
// MainActivity.java
DaggerLoginModuleComponent.builder()
.httpComponent(((TestApplication)getApplication()).getHttpComponent()).build().inject(this);
SettingActivity中的写法和MainActivity保持一致,然后运行一下:
04-28 16:25:50.715 11228-11228/com.testdes.des I/dagger2: MainActivity:com.testdes.des.dagger2.OkHttpClient@39f1d88
04-28 16:25:50.769 11228-11228/com.testdes.des I/dagger2: SettingActivity:com.testdes.des.dagger2.OkHttpClient@39f1d88
终于单例了,以上是dagger2的基本用法和单例的实现,谢谢~
注意事项
- component的inject方法接收父类型参数,而传入的是子类型的对象则无法注入
- component关联的modules中不能有重复的provide
- module中的provide方法使用了scope,则component需要使用同一个注解
- component的dependencies与component自身不能使用同一个scope,即组件之间的scope不能相同
- singleton的组件不能依赖其他scope组件,只能其他scope组件依赖singleton
- 没有scope的组件不能依赖有scope的组件
- @singleton的生命周期依附于component,同一个module provide singleton,不同的component也不是单例