Dagger2 使用
Dagger2是一款基于Java注解来实现的完全在编译阶段完成依赖注入的开源库,主要用于模块间解耦、提高代码的健壮性和可维护性。Dagger2在编译阶段通过apt利用Java注解自动生成Java代码,然后结合手写的代码来自动帮我们完成依赖注入的工作。
1. 依赖倒置
依赖倒置原则(Dependence Inversion Principle),程序要依赖抽象,而不是依赖具体实现。 A.高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。 B.抽象不应该依赖于具体,具体应该依赖于抽象。
什么是依赖关系? A类中要用的B类中的部分功能,A类就得持有一个B类对象,即A类依赖B类。
public class A {
private B mb;
public void action(){
mb.show();
}
}
复制代码
依赖有什么问题? 软件的需求都是不断变动的,当B类发生变动(比如B类删除了show方法),那A类对象要正常工作,就得跟着变动。耦合程度太高。
依赖抽象是什么意思? 高层次的模块不直接持有低层次模块的引用,而是持有一个接口类型的引用,低层次模块实现该接口;一般而言只要定义合理,接口是不怎么变动的,而低层次模块的实现变动不会影响到高层次模块对接口方法的调用。
2. 依赖注入
前面提到让A类依赖接口,但始终要传入一个实现了该接口的实例对象时才能让A类对象正常工作,通常有以下几种形式。
public class A {
// IB是个接口
private IB mb;
public A() {
// 在A类中生成
this.mb = new B();
}
public A(IB mb) {
// 构造A时传入B对象
this.mb = mb;
}
public void setMb(IB mb) {
// 使用setter来传入B对象实例
this.mb = mb;
}
public void action() {
mb.show();
}
}
复制代码
在A类外部构造好IB接口的实例对象,传入A对象的方式就是依赖注入。
3. Dagger2基本使用
Dagger2使用编译器注解生成代码来完成依赖注入,使用分为三大步: 引入依赖库
implementation 'com.google.dagger:dagger:2.21'
implementation 'com.google.dagger:dagger-android:2.21'
annotationProcessor 'com.google.dagger:dagger-compiler:2.21'
复制代码
- 构建依赖对象,在外部实现依赖对象的创建,Dagger2中负责提供依赖的组件被称为Module。使用
@Module
标记类,@Provides
标记方法,方法返回依赖对象实例。
@Module
public class MainModule {
@Provides
Person personProviders() {
System.out.println("a person created in MainModule ");
return new Person();
}
}
复制代码
- 创建Injector,用于将依赖对象注入到消费依赖对象的组件中去,Dagger2中称为Component。
@Component(modules = {MainModule.class})
public interface MainComponent {
void inject(MainActivity mainActivity);
}
复制代码
- 完成依赖注入,因为MainActivity依赖Person类,需要在MainActivity中构建Injector对象。
public class MainActivity extends AppCompatActivity {
@Inject
Person mPerson;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main2);
MainComponent mainComponent = DaggerMainComponent.builder().mainModule(new MainModule()).build();
mainComponent.inject(this);
}
}
复制代码
以上就是最简单的注入,Mudule是需求提供方,在其中创建需要注入的对象;MainActivity是需求方,在Activity中声明所需要的对象。MainComponent是连接彼此的桥梁。
下面解释一下上面用到的几个注解
@Inject 可用在三个地方
-
用于类成员变量前,标记该字段需要被注入一个对象。
@Inject Person mPerson; 复制代码
Component会找到合适的Module中合适的被
@Provides
标记的方法,调用该方法将对象注入。 -
用于构造方法前,在dagger2通过Module找不到一个方法来生成该类实例时,会调用该构造方法生成实例对象,并将该实例注入给需求方。
@Inject public Person() { ID = UUID.randomUUID().toString(); } 或者 @Inject Person(String name){ ID=UUID.randomUUID().toString(); this.name=name; } 复制代码
当然不能同时标记两个构造函数,不然会引起歧义。
-
用在普通方法前,当一个Activity中的成员变量都已经注入后,会调自动调用被标记的普通方法。如果该普通方法有参数,则也会从Module中寻找或通过构造函数来创建的方式来创建所需参数对象。
@Inject public void normalMethod(Person person) { Toast.makeText(this, "normalMethod" + person +Toast.LENGTH_LONG).show(); } 复制代码
@Module 用在Module类前面,被标记的类包含了一系列被@Provides
标记的方法,可以对外提供对象实例。
因为有些对象需要手动创建,不能通过给构造函数加@Inject
的方式完成,比如第三方库中的对象。
@Module
public class CcModule {
private ICommonView mICommonView;
public CcModule(ICommonView ICommonView) {
mICommonView = ICommonView;
}
@ActivityScope
@Provides
public ICommonView provideICommonView() {
return this.mICommonView;
}
}
复制代码
@Provides 用于标注Module所标注的类中的方法,该方法在需要提供依赖时被调用,从而把预先提供好的对象当做依赖给标注了@Inject
的变量赋值;
@Component 标记在接口上,是一个容器,指明了该Component包含的Module,以及该容器绑定到哪个组件上。一个Activity或Fragment只能注册一个Component,而该Component要满足能提供该Activity或Fragment中所需的所有要注入的对象才行。
@ActivityScope
@Component(modules = {PersonModule.class})
public interface PersonComponent {
void inject(MainActivity mainActivity);
}
复制代码
意思是PersonComponent中要能提供Mainactivity中所需的所有要注入的对象才行,不然编译不通过。 而且inject中的类型必须是实际类型。
@Singleton 单例模式,当我们需要创建一个单例对象时,使用该注解即可,方式如下:
// 1. 创建module,用@Singleton标记提供对象的方法。
@Module
public class PersonModule {
@Provides
@Singleton
public Person providePerson() {
return new Person("姓名","password",18);
}
}
// 2. 在module所在的Component上添加@Singleton
@Singleton
@Component(modules = {PersonModule.class})
public interface PersonComponent {
void inject(MainActivity mainActivity);
}
复制代码
如此,MainActivity中的注入的Person对象就是单例对象了,但这个单例模式的仅在MainActivity中有效,当PersonComponent被多个Activity使用时,每个Activity都会有一个Person单例对象,但从全局来看实际上实例化了多个Person对象。 所以,要创建全局单例,就只能在Application对应的Component中创建。
@dependencies 依赖其他Component,一个Component依赖其他Component,则该Component就包含了其他Component可以提供的对象。
@Component(modules = {CarModule.class}, dependencies = {PersonComponent.class})
public interface CarComponent {
void inject(MainActivity mainActivity);
}
复制代码
CarComponent依赖了PersonComponent,则CarComponent也有了提供Person相关对象的能力。依赖和java中继承很相似,子类“依赖”父类,就持有了父类的非私有成员变量。
@Named 解决注入对象的冲突,假设我们的Activity中需要两个不同类型的Person,有两个provide方法;但注入时Dagger是无法区分谁是谁的。使用@Named注解可以解决该问题。
@Module
public class PersonModule {
@Provides
@Named("man")
public Person provideManPerson() {
return new Person("男人", "password", 18);
}
@Provides
@Named("woman")
public Person provideWomanPerson() {
return new Person("女人", "passwd", 18);
}
}
// MainActivity中
@Inject
@Named("man")
Person mManPerson;
@Inject
@Named("woman")
Person mWomanPerson;
复制代码
@Qualifier 以上冲突解决方案,需要字符串一致,不够优雅容易出错。我们可以使用@Qualifier定义新的注解来解决这个问题。
// 1. 定义注解
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface Man {
}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface Woman {
}
// 2. Module中使用类型区分注解
@Module
public class PersonModule {
@Provides
@Man
public Person provideManPerson() {
return new Person("男人", "password", 18);
}
@Provides
@Woman
public Person provideWomanPerson() {
return new Person("女人", "passwd", 18);
}
}
// 3. MainActivity中使用类型区分注解
@Inject
@Man
Person mManPerson;
@Inject
@Woman
Person mWomanPerson;
复制代码
当然声明的注解也可以用在方法参数中。
@Scope 自定义范围注解,我们可以为不同的组件提供不同的范围注解,比如为Activity、Fragment、Application等不同组件提供不同的范围标签。
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {
}
复制代码