前言
当前基于2.38.1源码整理出来的一篇实际应用;
现在回头看这篇文章,其实发现内容其实真的不错(对几年前自己啃源码的认可),但是之前的文章的结构性确实有点“呵呵”,反正我回头看了才发现其中主要问题:①文章没有整体思路;②不够通俗易懂;
所以有了现在对dagger2实际应用的翻新之作,意在能让人更好的理解dagger2。
分三部分讲解:dagger2 、dagger2-android、 hilt
下面从dagger2主体到细节,并且运用实际案例逐一讲解各个注解的实际应用。
以下叙述的案例主体来源于aac的GithubBrawserSample,可自行下载查看。
一 依赖注入-DI
DI-Dependency Injection。
在java中我们正常使用一个对象:
A a = new A();
通过依赖注入:
(1)首先创建一个容器C对象,这个容器C对象生命周期和项目绑定-C容器随着项目的创建而创建,随着项目的销毁而销毁;容器中创建一个A对象:
public class C{
A a;
public static C c;
public static newInstance(){
if(c = null){ c = new C();}
return c;
}
public void instance(){
a = new A();
}
public A getA(){
return a;
}
}
我们可以在MyApplication的oncreate方法中通过C.newInstance()实例化C对象。
(2)当我们要使用A对象时,直接在C对象中获取到实例化的A即可,如下B对象要使用A对象了
public class B{
private A a = MyApplication.getC().getA();
...
}
这个就是一套依赖注入的思想。是不是感觉有点烧脑,我特么好好得new对象不香吗,这么麻烦。
确实。很多时候没必要这么去做,并不是每个项目都需要通过依赖注入去做的,而且实现也比较麻烦,也不容易懂。项目开发成本随之提高,尤其现在国内对Dagger2还是比较排斥的,用的不多。
那么为什么要使用依赖注入?
使用依赖注入原因:
1. **官方背景。**依赖注入是google推出来的,和aac一起推出来的;
2. **已得到广泛应用。**依赖注入思想其实在spring架构中已经被使用很久了(如果你们了解spring源码的话);
3. **(重点)易于结构层维护。**依赖注入更注重于架构方面的调整:是为了提高开发效率、易解耦。
对一个简单的项目,或者项目成员整体水平不高的情况下,依赖注入不但不会带来实质性帮助,反而起到束缚作用。但是对于一个大型项目来说,依赖注入的思想起到非常至关重要的作用,典型的就是插拔。
什么叫插拔?
嗯,用了一个框架(比如glide),感觉不符合当前项目需求了(想换一个图片加载机制),我要换一个,这个时候就体现依赖注入的重要性和方便性。
android实现依赖注入最好的办法就是Dagger2架构。
二 dagger2
(一)主体案例
AppComponent.kt
@Singleton
@Component(
modules = [//modules节点的作用就是往容器中实例化对象
...,
AppModule::class]
)
interface AppComponent {
@Component.Builder
interface Builder {
//传递参数
@BindsInstance
fun application(application: Application): Builder
//实例化AppComponent对象
fun build(): AppComponent
}
//将当前Appcomponent容器注入GithubApp中
fun inject(githubApp: GithubApp)
}
调用DaggerAppComponent.builder().application(githubApp).build().inject(githubApp)表示容器注入到GithubApp中。
AppModule.kt
@Module(includes = [ViewModelModule::class])//includes表示子module节点,同样表示往容器中实例化对象
class AppModule {
@Singleton
@Provides
fun provideGithubService(): GithubService {//实例化GithubService 对象
return Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(LiveDataCallAdapterFactory())
.build()
.create(GithubService::class.java)
}
@Singleton
@Provides
fun provideDb(app: Application): GithubDb {//实例化GithubDb 对象,当前方法参数来源于AppComponent 类的Builder类application方法
return Room
.databaseBuilder(app, GithubDb::class.java, "github.db")
.fallbackToDestructiveMigration()
.build()
}
@Singleton
@Provides
fun provideUserDao(db: GithubDb): UserDao {//实例化UserDao 对象
return db.userDao()
}
@Singleton
@Provides
fun provideRepoDao(db: GithubDb): RepoDao {//实例化RepoDao 对象
return db.repoDao()
}
}
AppModule类中实例化UserDao 和RepoDao,需要传递GithubDb参数:当前类中的provideDb方法提供了GithubDb参数,那么问题来了,我们如何确保GithubDb的实例化在UserDao 和RepoDao 实例化的前面?
其实Dagger源码在容器中实例化对象之前,会做一个排列。某某对象实例化需要引用到另外对象,做一个顺序排列,Dagger会很好的掌握对象实例化顺序。
AppModule.kt还包含一个子module节点
ViewModelModule.kt
@Suppress("unused")
@Module
abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(UserViewModel::class)
abstract fun bindUserViewModel(userViewModel: UserViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(SearchViewModel::class)
abstract fun bindSearchViewModel(searchViewModel: SearchViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(RepoViewModel::class)
abstract fun bindRepoViewModel(repoViewModel: RepoViewModel): ViewModel
@Binds
abstract fun bindViewModelFactory(factory: GithubViewModelFactory): ViewModelProvider.Factory
}
这个类主要是通过GithubViewModelFactory实例化UserViewModel、SearchViewModel和RepoViewModel对象;
bindUserViewModel方法参数通过class UserViewModel @Inject constructor(userRepository: UserRepository, repoRepository: RepoRepository) :Inject注解修饰构造函数实例化;
也就是说到目前为止,实例化对象可以通过
module节点的方法,还可以通过Inject修饰构造函数的方式。
(二)核心注解
这里主要针对:
1. (sub)component节点被这些注解修饰:@Component、@ProductionComponent、@Subcomponent、@ProductionSubcomponent;
2. creator节点被这些注解修饰:@Component.Factory、@ProductionComponent.Factory、@Subcomponent.Factory、@ProductionSubcomponent.Factory、@Component.Builder、@ProductionComponent.Builder、@Subcomponent.Builder、@ProductionSubcomponent.Builder;
- creator节点有两种模式:Factory和Builder,分别表示生成(sub)component容器类采用工厂模式和构建者模式;
3. module节点被这些注解修饰:@Module、@ProducerModule;
4. bindingMethod节点被这些注解修饰:@Provides、@Produces、@Binds、@Multibinds、@BindsOptionalOf、@BindsInstance;
5. 针对@Inject、@AssistedInject、@Assisted、@AssistedFactory注解解说。@AssistedInject、@Assisted和@AssistedFactory三个是Dagger新出现的注解,在一起使用。@Inject主要是用于修饰构造函数、变量和普通方法。
所有使用
Dagger注解,建议都不要使用private修饰,最好使用public修饰,这是铁律。
component节点
component节点表示使用@Component、@ProductionComponent修饰的节点。
subcomponent节点表示使用@Subcomponent或@ProductionSubcomponent修饰的节点。
(sub)component节点注解仅仅用于修饰类或接口,具体规则如下:
1. (sub)component节点上有且仅有一个@Component、@ProductionComponent、@Subcomponent或@ProductionSubcomponent注解修饰;
2. 如果(sub)component节点上使用了@CancellationPolicy注解修饰,那么当前(sub)component节点只能使用@ProductionSubcomponent或@ProductionComponent注解;
3. (sub)component节点只能是abstract抽象类或接口;
4. (sub)component节点最多只能存在一个creator节点;
5. (sub)component节点不能使用@Reusable修饰;
6. 如果(sub)component是一个kotlin文件,那么(sub)component节点中的componentMethod方法名不能使用java关键字;
7. (sub)component使用的注解的dependencies方法不允许收集module节点:
- @Component#dependencies(module.class)错误;
componentMethod方法
componentMethod表示(sub)component节点中的方法,仅仅针对abstract修饰(或接口方法)、非private、非static的(包括从父级类继承过来的)componentMethod方法做校验,规则如下:
1. componentMethod方法返回类型如果是subcomponent或subcomponent.creator,该方法最多只能出现一次。返回类型是subcomponent和subcomponent.creator不允许同时出现在一个(sub)component节点中;
2. componentMethod方法不能使用泛型,如果当前(sub)component节点是kotlin文件,那么注意componentMethod不能使用java关键字;
3. 如果componentMethod方法的returnType返回类型是subcomponent节点:
- (1)当前componentMethod方法的参数必须有并且是module节点(当前方法参数中的module节点类型只允许出现一次),并且这个module节点来源于(2)-subcomponent关联的module节点;
- (2)收集这个subcomponent节点关联的module节点:①subcomponentAnnotation#modules里面的module节点;②条件①module节点上的注解moduleAnnotation#includes里面的module节点;③条件①和条件②的module节点的父级module(使用了moduleAnnotation注解)节点;
4. componentMethod方法返回类型是subcomponent.creator节点,当前方法参数必须为空;
5. 方法返回类型不是subcomponent节点也不是subcomponent.creator节点,那么该方法参数最多只允许有一个,并且对无参和有一个参数的情况分别校验:
- (1)componentMethod方法无参,对当前componentMethod方法返回类型做依赖校验(该知识点过于复杂,有兴趣可以自行查看源码)
- (2)componentMethod方法返回类型不是subcomponent节点也不是subcomponent.creator节点,并且有且仅有一个参数,该componentMethod方法返回类型要么是void,要么参数类型和方法返回类型一致;
- 如上案例AppComponent的方法fun inject(githubApp: GithubApp):表示将当前容器注入到GithubApp中。
componentAnnotation#dependencies
目前知道的是componentAnnotation#dependencies里面的Dependency节点不允许是module节点。
这个属性显得太偏门,一般用的也比较少,
dagger2.38.1版本中就没有使用该注解,这里就简单提一下。
creator节点
creator节点使用@Component.Factory``、@ProductionComponent.Factory、@Subcomponent.Factory、@ProductionSubcomponent.Factory、@Component.Builder、@ProductionComponent.Builder、@Subcomponent.Builder、@ProductionSubcomponent.Builder修饰的节点,creator节点是(sub)component节点的内部类。
Factory工厂模式的案例如下:
@Singleton
@Component(
modules = {
BindingGraphValidationModule.class
}
)
interface ProcessorComponent {
void inject(ComponentProcessor processor);
static Factory factory() {
return DaggerComponentProcessor_ProcessorComponent.factory();
}
@Component.Factory
interface Factory {
@CheckReturnValue
ProcessorComponent create(
@BindsInstance XProcessingEnv xProcessingEnv,
@BindsInstance ImmutableSet<BindingGraphPlugin> externalPlugins
);//使用@BindsInstance传递参数
}
}
creator节点规则如下:
1. creator节点上的creatorAnnotation注解最多只能使用一个;
2. creator节点所在的父级节点一定是(sub)component节点;
3. creator节点只能是类或接口,并且如果是类的话,该类构造函数只能使用默认的;
4. creator节点不能使用泛型,并且creatorMethod不能使用private修饰;
5. 如果creator节点是factory类型:
- (1)非private、非static、abstract修饰的(包括继承的)的factoryMethod方法有且仅有一个;
- (2)factoryMethod方法不允许使用泛型;
- (3)factoryMethod方法返回类型必须是(sub)component节点或(sub)component的继承类;
- 注:factoryMethod方法返回类型是(sub)component节点的继承类,并且(sub)component节点中的如果没有componentMethod方法,会报警告;
- (4)factoryMethod方法不能使用@BindsInstance修饰;
- (5) factoryMethod方法参数要么是@BindsInstance修饰 || 要么不使用原始数据类型;
6. 如果creator节点是builder类型:
- (1)如果当前builder是kotlin文件,那么builderMethod不要使用了java的关键字;
- (2)builderMethod方法总共能有两种:有且仅有一个参数的setterMethod方法和无参的buildMethod方法:
- ① buildMethod方法有且仅有一个,并且不允许使用泛型类型;
- ② buildMethod方法返回类型必须是(sub)component节点或(sub)component继承类;
- 注:buildMethod方法返回类型是(sub)component节点的继承类,并且(sub)component节点中的如果没有componentMethod方法,会报警告;
- ③ buildMethod方法不允许使用@BindsInstance修饰;
- ④ setterMethod方法不能使用泛型,并且方法返回类型是void || builder节点及其子类;
- ⑤ setterMethod方法和方法参数不允许同时使用@BindsInstance修饰,setterMethod方法和方法参数有必要其中一个使用@BindsInstance修饰;
factory表示的是工厂模式,builder表示的是建造者模式,可以自行对照这两个模式去理解下。
@BindsInstance
@BindsInstance只允许修饰方法或方法参数。
1. @BindsInstance修饰方法参数:
- (1) 该参数不能使用FrameworkType架构类型:Provider,Lazy,MembersInjector,Produced,Producer;
- (2) 参数类型只能是原始数据类型或数组或接口或类或变量类型;
- (3)参数节点不能使用@Scope修饰的注解修饰;
- (4)参数所在方法必须是abstract修饰的抽象方法或接口中的非default方法;
- (5)@BindsInstance修饰的参数所在方法的返回类型,只能是一个类或接口(可以是泛型),不能是void、数组又或者原始类型
2. @BindsInstance修饰方法:
- (1)当前使用@BindsInstance修饰的方法校验:
- 注:@BindsInstance修饰的方法有且仅有一个参数;该参数和@BindsInstance修饰方法参数规则一致;
3. @BindsInstance修饰方法(或方法参数所在方法)的父级节点只能是creator节点;
也就是说**@BindsInstance修饰方法和方法参数区别在于修饰方法那么当前方法有且仅有一个参数,修饰方法参数那么当前方法参数不受限制**。
module节点
module节点表示使用@Module或@ProducerModule修饰的节点。规则如下:
1. module节点修饰类型取决于所在引用注解类型:
- 注:哪些注解引用module节点:①componentAnnotation#modules;②moduleAnnotation#includes;
- (1)如果componentAnnotation是production类型,那么componentAnnotation#modules里面的module节点既可以使用Module注解也可以使用ProducerModule注解;否则module节点只能使用Module注解修饰;
- (2)module节点是ProducerModule,那么moduleAnnotation#includes中的子module既可以使用Module注解也可以使用ProducerModule注解;否则子module节点只能使用Module注解修饰;
2. module节点可以使用泛型类型,如果使用了泛型,那么当前module节点要么是接口要么是abstract抽象类;但是module节点不能是Kotlin Companion Object类型;如果module节点中存在Kotlin Companion Object类型,该Kotlin Companion Object可以有bindingMethod方法;
3. module节点及其父节点最好使用public修饰,并且同一个module节点中的bindingMethod方法不能即出现abstract修饰的方法又存在非static修饰的实现方法;同一个module节点也不能出现同名的bindingMethod方法;bindingMethod不是重写也不能被重写;
4. module节点不能使用@Scope修饰的注解修饰,并且moduleAnnotation#subcomponents里面的subcomponent节点中必须有creator节点;
bindingMethod节点
1. bindingMethod绑定方法是module节点上使用@Provides 、@Produces 、@Binds 、@Multibinds、@BindsOptionalOf修饰的方法,并且一次只能使用五种中的一种:
- (1)该方法不允许使用泛型,不允许使用private修饰;
- (2)@Provides或@Produces修饰的bindingMethod方法必须使用实现方法;@Binds、@BindsOptionalOf或@Multibinds修饰bindingMethod方法必须使用抽象类型(abstract修饰或接口非default修饰的方法);
2. bindingMethod方法上使用@IntoSet、@IntoMap、@ElementsIntoSet:
- (1)如果是@Multibinds和@BindsOptionalOf不能使用这三种类型的绑定;
- (2)如果是@Provides、@Produces或@Binds只能使用@IntoSet、@IntoMap或@ElementsIntoSet其中的一种:
- ① 如果使用@ElementsIntoSet修饰,那么bindingMethod方法返回类型必须是Set;
- ② @IntoMap和@MapKey修饰的注解一定是成对出现的;
3. 只有@Binds和@Provides修饰的bindingMethod支持使用@Scope注解修饰的注解修饰,并且当前bindingMethod方法只允许出现一个@Scope注解修饰的注解;
4. bindingMethod方法如果是@Produces修饰,那么其所在module节点只能使用ProducerModule注解;其他四种类型bindingMethod的父级module节点既可以使用Module注解,也可以ProducerModule注解;
5. @Multibinds修饰的bindingMethod方法返回类型要么是Map<K,V>要么是Set< T>。
@Inject和@AssistedInject修饰构造函数
@Inject案例如下
class B @Inject constructor(...) {
...
}
通过@Inject修饰构造函数,等同于
@Module
class AppModule {
@Provides
fun provideB(): B {
return B(...)
}
}
class B constructor(...) {
...
}
实际使用都是方式一致,例如在A对象中使用B对象
class A{
@Inject
B b;
}
那么@Inject和bindingMethod的区别在什么地方?
复杂情况下,bindingMethod的选择性更强一些。
@Inject和 @AssistedInject案例
ProcessorComponent.class
@Component(
modules = {
ProcessingStepsModule.class//关联
}
)
interface ProcessorComponent {
//表示当前容器注入ComponentProcessor中使用@Inject修饰的变量实例对象
void inject(ComponentProcessor processor);
@Component.Factory
interface Factory {
@CheckReturnValue
ProcessorComponent create();
}
}
ProcessorComponent容器中引入ProcessingStepsModule中的实例
ProcessingStepsModule.class
@Module
interface ProcessingStepsModule {
@Provides
static XProcessingStep processingSteps(
ComponentProcessingStep componentProcessingStep
) {
return componentProcessingStep;
}
//processingSteps或者这么写
//@Binds
//XProcessingStep processingSteps(ComponentProcessingStep componentProcessingStep);
}
ProcessingStepsModule类中实例化的XProcessingStep对象实际上是ComponentProcessingStep,当前ComponentProcessingStep依赖于ComponentProcessingStep构造函数:
final class ComponentProcessingStep extends XProcessingStep {
@Inject
ComponentProcessingStep(
ComponentRequestRepresentations componentRequestRepresentations) {
this.componentRequestRepresentations = componentRequestRepresentations;
}
}
ComponentProcessingStep构造函数中的ComponentRequestRepresentations参数来源于ComponentRequestRepresentations类的构造函数:
public final class ComponentRequestRepresentations {
@Inject
ComponentRequestRepresentations(
LegacyBindingRepresentation.Factory legacyBindingRepresentationFactory) {
this.legacyBindingRepresentationFactory = legacyBindingRepresentationFactory;
LegacyBindingRepresentation legacyBindingRepresentation = legacyBindingRepresentationFactory.create(true,new Binding(),new SwitchingProviders());
}
}
LegacyBindingRepresentation.Factory来源:
final class LegacyBindingRepresentation{
@AssistedInject
LegacyBindingRepresentation(
@Assisted boolean isFastInit,
@Assisted Binding binding,
@Assisted SwitchingProviders switchingProviders,
...) {
}
@AssistedFactory
static interface Factory {
LegacyBindingRepresentation create(
boolean isFastInit,
Binding binding,
SwitchingProviders switchingProviders
);
}
}
如果我们要使用ComponentProcessingStep对象,如下:
public class ComponentProcessor {
@Inject
XProcessingStep processingStep;
}
下面我们对相关注解一一讲解。
@Inject和@AssistedInject修饰构造函数
@Inject和@AssistedInject修饰构造函数规则:
1. 节点的构造函数不允许同时使用Inject注解和AssistedInject注解;
2. @Inject、@AssistedInject都可以用来修饰构造函数,并且该构造函数不允许使用private修饰,也不能被@Qualifier修饰的注解修饰;
3. 被Inject或AssistedInject修饰的构造函数不能被@Scope注解修饰的注解修饰;
4. @Inject或@AssistedInject修饰的构造函数的参数不能是Produced< T>和Producer< T>类型,参数表示依赖;
5. 被@Inject或@AssistedInject修饰的构造函数如果throws异常,那么异常一定要是RuntimeException或Error或两者子类;
6. 使用了@Inject或@AssistedInject修饰的构造函数所在父节点不可以被private类使用,该构造函数所在父节点也不能使用abstract修饰,并且如果构造函数所在父节点是一个内部类,那么该内部类必须使用static修饰;
7. 一个类最多只能有一个构造函数被@Inject或@AssitedInject修饰;
8. 使用@AssistedInject修饰的构造函数所在的父节点不能被使用@Scope注解修饰的注解修饰。
@Inject修饰变量或普通方法规则
一般情况下用法最多的是
@Inject修饰修饰变量。表示当前变量需要通过从容器中获取实例化对象。
1. @Inject修饰的变量节点不能使用final修饰;也不要使用private和static修饰(可能警告可能报错);
2. @Inject修饰的普通方法必须是实现类,不能是abstract修饰的抽象类或接口方法;
3. @Inject修饰的普通方法不要使用private和static修饰(可能报错可能警告);
4. @Inject修饰的普通方法不能使用泛型类型,并且不能throws异常;
5. @Inject修饰的节点所在父节点最好不要被private修饰(可能警告可能报错);并且@Inject修饰的节点所在父节点不能是Kotlin Object或Kotlin Companion Object对象;
@AssistedInject、@Assisted和@AssistedFactory在一起!!!
@AssistedInject仅仅用于修饰构造函数,规则上面已经说了,自行查看。
@Assisted修饰的节点规则如下:
1. @Assisted只能修饰方法参数,并且仅仅满足以下条件:
- (1)@Assisted修饰的参数,位于一个被@AssistedInject修饰的构造函数中;
- (2)@Assisted修饰的参数所在的方法 ,如果该方法命名包含"copy" ,那么该方法所在类是一个data类型的kotlin文件;
2. @Assisted修饰的参数节点不能被@Qualifier修饰的注解修饰;
3. @AssistedInject修饰的构造函数或@AssistedFactory修饰的节点中的普通方法都不允许出现重复的@Assisted修饰的参数类型。
@AssistedFactory修饰的节点中的方法仅仅针对abstract(接口除外)、非static、非private修饰的方法校验,@AssistedFactory注解使用规则如下:
1. 使用@AssistedFactory修饰的节点仅仅支持抽象类或接口;
2. 如果@AssistedFactory修饰的节点是内部类,那么必须使用static修饰;
3. @AssistedFactory修饰的节点必须有且仅有一个abstract、非static、非private的方法节点;
4. @AssistedFactory修饰的节点中的方法返回类型的构造函数必须使用@AssistedInject修饰;
5. @AssistedFactory修饰的节点中的方法不允许使用泛型;
6. @AssistedFactory修饰的节点中的唯一方法传递的参数 和 该方法返回类型中的构造函数使用@Assisted注解修饰的参数 保持一致。
(三)其他注解
这里主要针对@MapKey、@Scope、@Qualifier注解还有@IntoSet,@IntoMap和@ElementsIntoSet的理解。
@MapKey、@Scope、@Qualifier三个注解仅仅可用于修饰注解。@MapKey是Dagger自定义的,@Scope和@Qualifier是javax.inject下的注解。
@Scope
我们直译过来表示范围,这个注解是用于修饰注解的注解。
@Scope修饰的注解具体使用规则:
1. 节点使用@Scope修饰的注解最多只允许出现一个;
2. 使用@Module或@ProducerModule修饰的module节点不允许使用@Scope修饰的注解修饰;
3. 绑定方法bindingMethod节点。其中@Provides和@Binds修饰的方法允许同时使用@Scope修饰的注解修饰,@Produces、@Multibinds和@BindsOptionalOf修饰的方法不允许同时使用@Scope修饰的注解修饰;
4. @Inject或@AssistedInject修饰的构造函数不允许使用同时使用@Scope修饰的注解修饰;并且@AssistedInject修饰的构造函数所在的父级节点不允许使用使用@Scope修饰的注解修饰,但是使用@Inject修饰的构造函数所在父级节点允许使用@Scope修饰的注解修饰;
5. @BindsInstance修饰的方法或方法参数不允许同时使用@Scope修饰;
@Scope表示一个作用域,表示当前@Scope修饰的注解修饰的节点所属的currentcomponent节点及其往下的subcomponent节点都可以使用该节点。
我们以@Singleton为例:
Module节点,里面包含Subcomponent1,Subcomponent2,Subcomponent3几个subcomponent节点,Component节点中关联Module节点,Component节点的入口方法inject的作用是容器注入MembersInjector类中:
@Module(subcomponents = Subcomponent1.class,Subcomponent2.class,Subcomponent3.class)
public class Module{
@Provides
@Singleton
public A bindingMethod(B b){
return new A(b);
}
}
@Component(modules = Module.class)
public calss Component{
inject(MembersInjector m);
public SubcomponentX getSubcomponentX();//这个也是一个Subcomponent节点
}
public class MembersInjector{
@Inject
public C C;
}
@Singleton
public class C {
@Inject
public C(){}
}
已上面的实例解说如下:
1. 当前@Singleton和@Provides修饰的bindingMethod方法返回的A的实例化既可以在当前Component作用域中使用,也可以在Subcomponent1,Subcomponent2,Subcomponent3和SubcomponentX(当然了,在Subcompoonent中的实例化需要的A实例化对象的情况下)使用;
2. 当前@Singleton修饰的C类实例化对象既可以在当前Component作用域中使用也可以在Subcomponent1,Subcomponent2,Subcomponent3和SubcomponentX中使用;
3. 那如果在Subcomponent1中定义了@Singleton修饰的节点,那么该节点的实例对象是不能再父级fathercomponent中使用的。
为什么有好几种@Scope修饰的注解,有什么不同?
Dagger当前2.38.1版本中源码中有几个@Scope修饰的注解,@Reusable、@PerGeneratedFile、@PerComponentImplementation、@ProductionScope;还有javax.inject包下有一个@Singleton;
1. @Reusable:@Reusable和@Binds同时修饰一个module节点中的bindingMethod方法,那么当前bindingMethod方法参数类型A节点如果没有使用@Scope修饰的注解,那么直接使用SingleCheck;如果A节点使用了@Scope修饰的注解,那么使用DoubleCheck;
- SingleCheck和DoubleCheck的区别,SingleCheck单例获取A对象;DoubleCheck不仅仅获取A对象,并且校验当前A对象的实例化始终都是同一个实例;
@Binds
@Reusable
// to avoid parsing options more than once
CompilerOptions bindCompilerOptions(
ProcessingEnvironmentCompilerOptions processingEnvironmentCompilerOptions
);
- 如上代码案例,ProcessingEnvironmentCompilerOptions如果没有使用@Scope修饰的注解修饰,那么是SingleCheck单例,否则是DoubleCheck双重校验(单例 + 校验必须同一实例),其实对我们来说好像并没有多大影响;
2. @Reusable其他情况和@Singleton用法都是在作用域中有DoubleCheck双重校验;
3. @PerGeneratedFile表示将当前修饰的节点及其往下关联的节点和当前所属的currentcomponent生成的代码写在同一个文件中,没有实际意义;
4. @PerComponentImplementation:Dagger内部处理代码,相当于一个A类,但是这个A可能包含自己,那么我们将另外开辟一个用于生成子类A的实例,需要用到@PerComponentImplementation;
5. @ProductionScope:如果使用了@ProductionComponent或@ProductionSubcomponent修饰的component节点,那么Dagger为我们生成一个@Module修饰的module节点,该module节点中有monitor方法如下:
@Provides
@ProductionScope
static ProductionComponentMonitor monitor(Provider<component节点类型> component,Provider<Set<ProductionComponentMonitor.Factory>> factories){
return Monitors.createMonitorForComponent(component, factories);
}
- ProductionScope实际作用还是当前component的作用域,这里使用monitor方法的目的在于整个@ProductionComponent或@ProductionSubcomponent修饰的component节点的监控。
@Qualifier
直译过来是预选的意思。@Qualifier修饰的注解使用规则如下:
1. 节点上只允许出现一个@Qualifier修饰注解的注解;
2. componentMethod方法只有在返回类型是subcomponent节点或subcomponent.creator节点情况下才允许使用@Qualifier修饰的注解修饰;
3. bindingMethod方法可以被@Qualifier修饰的注解修饰;
4. 被@Inject或@AssistedInject修饰的构造方法不允许使用@Qualifier修饰的注解修饰,但是构造函数的参数可以;
5. @Assisted修饰的参数节点不能被@Qualifier注解修饰的注解修饰;
6. @MembersInjector的成员注入T类型不允许使用@Qualifier修饰的注解修饰;
7. 一个节点允许使用@Qualifier修饰,那么当前节点类型(如果是方法,则表示方法返回类型)的构造函数允许使用@AssistedInject修饰。
@Qualifier修饰的注解主要是为了实例化过程中成功匹配到正确的参数,我们以案例说话:
@Retention(RUNTIME)
@Qualifier
public @interface ProcessingOptions {}
public final class ExternalBindingGraphPlugins {
@Inject
ExternalBindingGraphPlugins(...,
@ProcessingOptions Map<String, String> processingOptions) {
}
}
@Module
interface ProcessingEnvironmentModule{
@Provides
@ProcessingOptions
static Map<String, String> processingOptions(XProcessingEnv xProcessingEnv) {
return xProcessingEnv.getOptions();
}
@Provides
static Map<String, String> getMap(String str) {
return new Map<String,String>();
}
}
如上图所示,ExternalBindingGraphPlugins对象的实例化需要 Map<String, String> 对象,这里有两个Map<String,String>,那么需要靠@ProcessingOptions精确匹配。
- 在容易产生类型混淆的情况下会用到@Qualifier修饰的注解,这个也非常容易理解。
@IntoSet、@IntoMap和@ElementsIntoSet
@IntoSet、@IntoMap和@ElementsIntoSet用于修饰方法,规则如下:
1. 不允许同时使用,一个方法上仅仅可以使用@IntoSet、@IntoMap和@ElementsIntoSet中的一个;
2. 必须在bindingMethod绑定方法上使用,并且只支持@Provides、@Produces或@Binds三种类型的bindingMethod方法;
3. 如果使用了@ElementsIntoSet,那么当前bindingMethod方法返回类型必须是Set< T>;
4. @IntoMap 和@MapKey修饰的注解一定是成对出现的;
@IntoSet
收集同一种类型对象,下面给个案例了解下:
@Module
public interface BindingMethodValidatorsModule {
//下面的都是该方法参数
@Provides
static ImmutableMap<ClassName, BindingMethodValidator> indexValidators(
Set<BindingMethodValidator> validators) {
return uniqueIndex(validators, BindingMethodValidator::methodAnnotation);
}
@Binds
@IntoSet
BindingMethodValidator provides(ProvidesMethodValidator validator);
@Binds
@IntoSet
BindingMethodValidator produces(ProducesMethodValidator validator);
@Binds
@IntoSet
BindingMethodValidator binds(BindsMethodValidator validator);
@Binds
@IntoSet
BindingMethodValidator multibinds(MultibindsMethodValidator validator);
@Binds
@IntoSet
BindingMethodValidator bindsOptionalOf(BindsOptionalOfMethodValidator validator);
}
@Binds修饰的bindingMethod方法有且仅有一个参数,并且参数类型一定是方法返回类型或返回类型子类。
如上所示,Set< BindingMethodValidator> validators用于收集所有的BindingMethodValidator类型对象;
延伸:这里的@Binds当然可以改成@Provides,e.g. :
@Provides
@IntoSet
static BindingMethodValidator provides(XX xx){
return new ProvidesMethodValidator(xx);
}
@ElementsIntoSet
和@IntoSet用法基本一致, @ElementsIntoSet返回类型必须是Set< T>,如上我们可以改成使用@ElementsIntoSet修饰:
@Module
public interface BindingMethodValidatorsModule {
//下面的都是该方法参数
@Provides
static ImmutableMap<ClassName, BindingMethodValidator> indexValidators(
Set<BindingMethodValidator> validators) {
return uniqueIndex(validators, BindingMethodValidator::methodAnnotation);
}
@Binds
@ElementsIntoSet
Set<BindingMethodValidator> provides(ProvidesMethodValidator validator);
@Binds
@ElementsIntoSet
Set<BindingMethodValidator> produces(ProducesMethodValidator validator);
@Binds
@ElementsIntoSet
Set<BindingMethodValidator> binds(BindsMethodValidator validator);
@Binds
@ElementsIntoSet
Set<BindingMethodValidator> multibinds(MultibindsMethodValidator validator);
@Binds
@ElementsIntoSet
Set<BindingMethodValidator> bindsOptionalOf(BindsOptionalOfMethodValidator validator);
}
还有一个,
@ElementsIntoSet可以返回空绑定,但是@IntoSet不会。基本@IntoSet用的比较多,@ElementsIntoSet感觉像是一个附带产品。
@IntoMap
@IntoMap当然也是收集同一类对象,但是@IntoMap和@MapKey修饰的注解是成双成对出现的。why?@IntoMap是为了将当前类型T收集到Map<K,T>中,那么必须由@MapKey修饰的注解提供K。
再来举个例子:
AndroidInjectionModule类:
@Beta
@Module
public abstract class AndroidInjectionModule {
@Multibinds
abstract Map<Class<? extends Activity>, AndroidInjector.Factory<? extends Activity>>
activityInjectorFactories();
...
}
MainActivityModule_ContributeMainActivity类:
@Module(subcomponents = MainActivityModule_ContributeMainActivity.MainActivitySubcomponent.class)
public abstract class MainActivityModule_ContributeMainActivity {
private MainActivityModule_ContributeMainActivity() {}
@Binds
@IntoMap
@ActivityKey(MainActivity.class)
abstract AndroidInjector.Factory<? extends Activity> bindAndroidInjectorFactory(
MainActivitySubcomponent.Builder builder);
@Subcomponent(modules = FragmentBuildersModule.class)
public interface MainActivitySubcomponent extends AndroidInjector<MainActivity> {
@Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<MainActivity> {}
}
}
多提一句,这里的MainActivityModule_ContributeMainActivity是通过如下代码生成出来的:
@Suppress("unused")
@Module
abstract class MainActivityModule {
@ContributesAndroidInjector(modules = [FragmentBuildersModule::class])
abstract fun contributeMainActivity(): MainActivity
}
AndroidInjectionModule类的activityInjectorFactories方法用于收集用于收集Map<Class<? extends Activity>, AndroidInjector.Factory<? extends Activity>>,MainActivityModule_ContributeMainActivity类中的bindAndroidInjectorFactory方法@ActivityKey(MainActivity.class)提供K:MainActivity.class,V:MainActivitySubcomponent.Builder,符合被收集的条件。
@MapKey
当前使用@Mapkey修饰的注解,满足如下规则:
1. 如果MapKey.unwrapValue() = true的情况下(默认情况下就是true),被修饰的注解有且仅有一个方法,并且该方法返回类型不能是数组类型;
- e.g.ClassKey注解
2. 如果MapKey.unwrapValue() = false,那就麻烦了,被修饰的注解方法不但不限制,而且我们的项目中必须引入了com.google.auto.value.AutoAnnotation依赖,这是什么骚操作???
- (1)引入AutoAnnotation的目的是对MapKey修饰的注解的所有方法返回类型作为变量生成一个新的类T,相当于MapKey.unwrapValue() = true的情况下的 @MapKey(T.class);
- (2)所以MapKey.unwrapValue() = false可以使用MapKey.unwrapValue() = true替代,而且MapKey.unwrapValue() = true更容易理解、实现也更加稳健。非必要不要使用MapKey.unwrapValue() = false;
3. @MapKey和@IntoMap是情侣关系,必须在一起!!!
三 dagger2-android
主要针对@AndroidInjectionKey和@ClassKey两个注解、@ContributesAndroidInjector注解的处理。
(一) 案例
DaggerAppComponent.builder().application(githubApp).build().inject(githubApp)和以上主体案例仅仅是将当前实例化后的容器注入到自定义Application中,那么如何在具体的Activity或Fragment中使用容器中的实例化对象,如下:
AppComponent
@Singleton
@Component(
modules = [
AndroidInjectionModule::class,
...,
MainActivityModule::class]
)
interface AppComponent {...}
`MainActivityModule`
@Suppress("unused")
@Module
abstract class MainActivityModule {
@ContributesAndroidInjector(modules = [FragmentBuildersModule::class])
abstract fun contributeMainActivity(): MainActivity
}
FragmentBuildersModule
@Suppress("unused")
@Module
abstract class FragmentBuildersModule {
@ContributesAndroidInjector
abstract fun contributeRepoFragment(): RepoFragment
@ContributesAndroidInjector
abstract fun contributeUserFragment(): UserFragment
@ContributesAndroidInjector
abstract fun contributeSearchFragment(): SearchFragment
}
GithubApp
class GithubApp : Application(), HasActivityInjector {
@Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
...
override fun activityInjector() = dispatchingAndroidInjector
}
MainActivity
class MainActivity : AppCompatActivity(), HasSupportFragmentInjector {
@Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
...
override fun supportFragmentInjector() = dispatchingAndroidInjector
}
AppInjector
object AppInjector {
fun init(githubApp: GithubApp) {
DaggerAppComponent.builder().application(githubApp).build().inject(githubApp)
githubApp
.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks {
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
handleActivity(activity)
}
override fun onActivityStarted(activity: Activity) {
}
override fun onActivityResumed(activity: Activity) {
}
override fun onActivityPaused(activity: Activity) {
}
override fun onActivityStopped(activity: Activity) {
}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
}
override fun onActivityDestroyed(activity: Activity) {
}
})
}
private fun handleActivity(activity: Activity) {
if (activity is HasSupportFragmentInjector) {
AndroidInjection.inject(activity)
}
if (activity is FragmentActivity) {
activity.supportFragmentManager
.registerFragmentLifecycleCallbacks(
object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentCreated(
fm: FragmentManager,
f: Fragment,
savedInstanceState: Bundle?
) {
if (f is Injectable) {
AndroidSupportInjection.inject(f)
}
}
}, true
)
}
}
}
需要强调一点,以上所做的目的是在Activity和Fragmen中能够使用到注入自定义Application的Component容器中的实例化对象。
完成以上步骤,我们就可以在Fragment中使用容器中的实例化对象了,用法如下:
class RepoFragment : Fragment() {
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
@Inject
lateinit var appExecutors: AppExecutors
...
}
(二)注解使用
@ContributesAndroidInjector
@ContributesAndroidInjector注解用于修饰方法。@ContributesAndroidInjector修饰的方法校验规则如下:
1. 方法必须使用abstract修饰(按理说修饰的是接口方法也ok的);
2. 方法不能有参数;
3. 方法的父级类必须是@Module修饰的module节点;
4. 该方法的返回类型不能使用泛型;
5. @ContributesAndroidInjector#modules里面的节点必须使用@Module注解;
6. 该方法不能使用@Qualifier修饰的注解修饰;
我们以下面的案例来说下@ContributesAndroidInjector修饰的方法的作用:
@Module
abstract class MainActivityModule {
@ContributesAndroidInjector(modules = [FragmentBuildersModule::class])
abstract fun contributeMainActivity(): MainActivity
}
通过Dagger的android模块生成如下代码:
@Module(subcomponents = MainActivityModule_ContributeMainActivity.MainActivitySubcomponent.class)
public abstract class MainActivityModule_ContributeMainActivity {
private MainActivityModule_ContributeMainActivity() {}
@Binds
@IntoMap
@ActivityKey(MainActivity.class)
abstract AndroidInjector.Factory<? extends Activity> bindAndroidInjectorFactory(
MainActivitySubcomponent.Builder builder);
@Subcomponent(modules = FragmentBuildersModule.class)
public interface MainActivitySubcomponent extends AndroidInjector<MainActivity> {
@Subcomponent.Builder
abstract class Builder extends AndroidInjector.Builder<MainActivity> {}
}
}
bindAndroidInjectorFactory方法参数依赖于:
@Module
public abstract class AndroidInjectionModule {
@Multibinds
abstract Map<Class<? extends Activity>, AndroidInjector.Factory<? extends Activity>>
activityInjectorFactories();
...
}
@AndroidInjectionKey 和 @ClassKey
@AndroidInjectionKey和@ClassKey本身就是使用@MapKey注解修饰的注解。
@AndroidInjectionKey注解只能修饰方法,@ClassKey注解可以修饰方法和变量。
我们仅仅针对@AndroidInjectionKey或@ClassKey修饰的方法做校验工作,校验规则如下:
1. @AndroidInjectionKey 和 @ClassKey 必须满足@MapKey注解规则;
2. 如果方法使用了@AndroidInjectionKey或@ClassKey修饰,那么当前方法不能使用@Qualifier修饰的注解修饰;
3. 使用@AndroidInjectionKey或@ClassKey修饰的方法返回类型如果不是AndroidInjector.Factory及其子类,不需要继续校验了,该方法直接被忽略;
4. 如果该方法使用了@Scope注解修饰的注解修饰,该方法必须使用@SuppressWarnings注解修饰,并且该@SuppressWarning注解包含dagger.android.ScopedInjectorFactory值;
5. 方法返回类型必须是AndroidInjector.Factory类型;
6. 如果方法使用了@Binds修饰并且方法参数有且仅有一个,当前方法参数类型必须是AndroidInjector.Factory的子类,这里的T表示@AndroidInjectionKey或@ClassKey注解的方法类型,如下案例:
@Binds
@IntoMap
@ClassKey(GreenActivity.class)
abstract AndroidInjector.Factory<?> bindBlueActivity(
BlueActivityComponent.Builder builder);
}
BlueActivityComponent.Builder是AndroidInjector.Factory< GreenActivity>子类
四 总结
以上谈不上通俗易懂(没办法,dagger本身就挺复杂,再加上自己实力有限),主要通过demo的形式讲解了Dagger在android上的使用,下一章对hilt的使用就行讲解。
可在QQ群:575306647 讨论
以上根据源码分析得出的结论,有不足之处请指教!