Dagger2:上手就爱不释手

概览:

  • 什么是Dagger2?
    既然有Dagger2那么必然有Dagger1的存在,Dagger1是大名鼎鼎的Square公司受到Guice启发而开发的依赖注入框架,而Dagger2是Dagger1的分支,由谷歌公司接手开发。

  • 为什么使用Dagger2?
    1.有利于模块间的解耦,组件依赖的注入独立于业务模块之外
    2.能够清晰地管理实例的有效范围
    3.因为对象的实例化独立于业务,所以当对象的实例化方法改变时,不需要大量地修改业务代码
    4.在编译期即完成依赖的注入,完全静态(说白了就是抛弃了反射)

深入研究

前言讲了那么多,接下来着重从原理和代码方面进行分析

引入Dagger2

Dagger2的引入也非常方便,首先在项目的gradle中配置:

 dependencies { 
         classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4' 
}```   
然后在app的gradle中配置:

apply plugin: 'com.neenbedankt.android-apt'
...
dependencies {
apt 'com.google.dagger:dagger-compiler:2.0'
compile 'com.google.dagger:dagger:2.0'
}

可以看到gradle中出现了apt相关的配置,可以猜到Dagger2的生成与apt技术相关,即通过定义编译期的注解,再通过继承Proccesor生成代码逻辑,有兴趣的同学可以深入学习一番

###  使用详解

引入了Dagger2之后,首先解释一下Dagger2中的四种常用用法
- _ @Inject_  
主要有两种用法:一是标注相关的构造方法,那么Dagger2在使用时会找到标注有@Inject的构造方法来实例化;第二种是标注在相关的变量,告知Dagger2为其提供依赖

- _@Component_ 
@Component用于标注接口,是依赖需求方和依赖提供方之间的桥梁。被Component标注的接口在编译时会生成该接口的实现类(如果@Component标注的接口为ApplicationComponent,则编译期生成的实现类为DaggerApplicationComponent),我们通过调用这个实现类的方法完成依赖注入;
- _@Module_ 
@Module用于标注提供依赖的类,但涉及到一些第三方包中的构造方法,或者有参数的构造方法时,我们无法使用@Inject,转而使用@Module进行标注

- _@Provide_
@Provide
用于标注@Module中的方法,提供实例化的操作,并在需要时将依赖注入标注了@Inject的变量

随后这几种注解结合上述基本用法,有意想不到的妙用
- _@Qulifier_ 
@Qulifier 为了区分不同使用不同类型生成的实例,比如@ForActivity 或@ForApplication

- _@Scope_ 
可限定变量的生效范围,形成局部单例

- _@Singleton_ 
这个就比较好理解了,Application级别的单例

只是介绍了基本用法,可能理解起来还是一头雾水,我们接下来结合代码进行进一步阐述

1.仅使用@Inject
首先定义实体类Coke,并用@Inject标注构造方法

public class Coke {
@Inject
public Coke() {
Log.d("process", "a cup of coke was made");
}
}


第二步创建Component,命名为CokeComponent。inject方法即代表依赖注入至MainActivity

@Component
public interface CokeComponent
{
void inject(MainActivity activity);
}


在MainActivity中定义变量,并用@Inject标注
```java
public class MainActivity extends AppCompatActivity 
{   
//定义变量并标注
@Inject  Coke coke;    
@Override    
protected void onCreate(Bundle savedInstanceState) 
{
  super.onCreate(savedInstanceState);        
  setContentView(R.layout.activity_main); 
//DaggerCokeComponent由CokeComponent生成,调用下面的方法实现依赖注入
  DaggerCokeComponent.builder().build().inject(this);    
}
}

运行程序,则log中打印出:

D/process: a cup of coke was made

然而事实上,我们更多遇到是构造函数含参或者无法直接打上@Inject标记的情况,那么此时就要使用@Module进行依赖注入

2.结合@Module使用

假设我们现在制造一瓶可乐的构造方法为:public Coke(String ingredient),然后创建一个Module,并命名为CokeModule。用@Provide标注提供实例的方法,并在方法体内加入实例化相关的代码

@Module  
public class CokeModule
{    
public CokeModule() {} 
   
@Provides    
Coke provideCoke()  {        
    return new Coke("with suger");    
  }  
}

并对Component进行一点修改,告知Dagger2使用CokeModule提供依赖

@Component (modules = {CokeModule.class})
public interface CokeComponent {    
void inject(MainActivity activity);
}

运行程序,结果为:

D/process: a cup of coke was made with suger

3.结合@Qulifier使用

首先用@Qulifier定义两个注解

@Qualifier
@Retention (RetentionPolicy.RUNTIME)
public @interface QulifierNonSuger {}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface QulifierSuger {}

其次对CokeModule进行一点改造,支持两种实例化方式

@Module
public class CokeModule {   
  public CokeModule() {}    
@QulifierSuger    
@Provides   
Coke provideCokeWithSuger() {        
return new Coke("with suger");   
 }    
@QulifierNonSuger    
@Provides    
Coke provideCokeWithoutSuger() {        
    return new Coke("without suger");    
 }
}

在MainActivity创建两个Coke变量,并分别用两种Qulifier注解

@QulifierSuger 
@Inject
Coke cokeA;

@QulifierNonSuger
@Inject
Coke cokeB;

运行程序,两杯不同的可乐就制造出来啦

D/process: a cup of coke was made with suger
D/process: a cup of coke was made without suger

@Qulifier.png

通过上图,我们可以清晰地看出,Module中通过两种Qulifier注解表示了两种实例化对象的方法,Component通过Module中提供的方法将依赖注入标注了不同Qulifier的两个变量

4.结合@Scope使用

好吧,我们继续进行改造~
第一步,定义注解ActivityScope

@Scope
@Retention (RetentionPolicy.RUNTIME)
public @interface ActivityScope {}

然后用该Scope标注Component和Module

@Module
public class CokeModule {        
public CokeModule() {}    
@ActivityScope    
@Provides    
Coke provideCokeWithSuger() {        
return new Coke("with suger");    
  }
}
@ActivityScope
@Component(modules = {CokeModule.class})
public interface CokeComponent {   
 void inject(MainActivity activity);
}

我们在MainActivity中定义了两个Coke变量,但可以看到Log只打印了一次,即代表Coke变量在MainActivity范围内是局部单例的

5.扩展应用

Dependent Components.png

如图,Component之间也可以有依赖关系,Dagger2的实际运用中,我们常定义一个应用级别单例的ApplicationComponent来保持对Application的依赖,根据不同粒度(Activity级别或者页面级别)创建依赖于ApplicationComponent的其余Component进行整个应用的依赖注入控制

至此,一个简单的依赖注入使用案例已经结束,但大多数的同学应该还意犹未尽吧,因此我们接下来从Dagger2自动生成的代码层面深入了解Dagger2是如何工作的。

原理探究

这里我们以例子中的情况2作样本,Dagger2生成的代码可以在Build->generated->source->apt中找到

  • CokeModule_ProvideCokeWithSugerFactory

@Generated("dagger.internal.codegen.ComponentProcessor")
public final class CokeModule_ProvideCokeWithSugerFactory implements Factory<Coke> { 
 private final CokeModule module; 
 public CokeModule_ProvideCokeWithSugerFactory(CokeModule module) {     
  assert module != null;    
  this.module = module;  
  }  
@Override  public Coke get() {  Coke provided = module.provideCokeWithSuger();     
 if (provided == null) {     
 throw new NullPointerException("Cannot return null from a non-@Nullable @Provides method");    
}    
  return provided;  }  
public static Factory<Coke> create(CokeModule module) {      
return new CokeModule_ProvideCokeWithSugerFactory(module);  
  }
}

代码比较简单,构造方法中传入了CokeModule,get()中通过我们在Module创建的provideCokeWithSugaer()拿到Coke的实例,而create方法我们稍后进行解释

  • MainActivity_MembersInjector
@Generated("dagger.internal.codegen.ComponentProcessor")
public final class MainActivity_MembersInjector implements MembersInjector<MainActivity> {  
private final MembersInjector<AppCompatActivity> supertypeInjector;  
private final Provider<Coke> cokeAndCokeAProvider;  
public MainActivity_MembersInjector(MembersInjector<AppCompatActivity> supertypeInjector, Provider<Coke> cokeAndCokeAProvider) {     
assert supertypeInjector != null;    
this.supertypeInjector = supertypeInjector;    
assert cokeAndCokeAProvider != null;   
this.cokeAndCokeAProvider = cokeAndCokeAProvider;  
  }  
@Override  public void injectMembers(MainActivity instance) {     
 if (instance == null) {      
throw new NullPointerException("Cannot inject members into a null reference");    }    
//依赖注入
supertypeInjector.injectMembers(instance);     
instance.coke = cokeAndCokeAProvider.get();  
  }  
public static MembersInjector<MainActivity> create(MembersInjector<AppCompatActivity> supertypeInjector, Provider<Coke> cokeAndCokeAProvider) {       
 return new MainActivity_MembersInjector(supertypeInjector, cokeAndCokeAProvider);  
  }
}

构造方法中传入了Injector和Provider,injectMembers()中将instance(MainActivity)通过回调传给Injector,并从Provider中取出Coke的实例赋值给instance。代码最后又出现了create,我们可以肯定有一个桥梁将两者串联在一起,那就是——请继续往下看

  • DaggerCokeComponent
@Generated("dagger.internal.codegen.ComponentProcessor")
public final class DaggerCokeComponent implements CokeComponent {  
private Provider<Coke> provideCokeWithSugerProvider;  
private MembersInjector<MainActivity> mainActivityMembersInjector;  
private DaggerCokeComponent(Builder builder) {      
assert builder != null;    
initialize(builder);  
}  
public static Builder builder() {      
return new Builder();  
}  
public static CokeComponent create() {      
return builder().build();  
}  
private void initialize(final Builder builder) {      
this.provideCokeWithSugerProvider = ScopedProvider.create(CokeModule_ProvideCokeWithSugerFactory.create(builder.cokeModule));    
this.mainActivityMembersInjector = MainActivity_MembersInjector.create((MembersInjector) MembersInjectors.noOp(), provideCokeWithSugerProvider);  }  
@Override  public void inject(MainActivity activity) {   
  mainActivityMembersInjector.injectMembers(activity);  
}  
public static final class Builder {    
private CokeModule cokeModule;      
private Builder() {      }     
public CokeComponent build() {       
 if (cokeModule == null) {        
this.cokeModule = new CokeModule();      
}      
return new DaggerCokeComponent(this);    
}      
public Builder cokeModule(CokeModule cokeModule) {        
if (cokeModule == null) {        
throw new NullPointerException("cokeModule");      
}      
this.cokeModule = cokeModule;      
return this;    
    }  
  }
}

谜底揭开了,结合我们在MainActivity中注入依赖的方法:
DaggerCokeComponent.builder().cokeModule(new CokeModule()).build().inject(this)
在Builder中,传入创建的CokeModule,可以看到Dagger2贴心地帮我们做了缺省设置,直接调用build()也可以生成CokeModule。然后调用DaggerCokeComponent的构造方法,在initialize中我们终于看到了刚才的两个create方法,也就是通过这座桥梁完成了实例的提供和注入。

后记

1.文中的图摘自CodePath Android Cliffnotes,原文为:Dependency Injection with Dagger 2
2.注意事项:当更新Dagger2的版本时,需重新clean项目,否则会出现actual and former argument lists different in length

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,875评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,569评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,475评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,459评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,537评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,563评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,580评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,326评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,773评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,086评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,252评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,921评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,566评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,190评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,435评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,129评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,125评论 2 352

推荐阅读更多精彩内容

  • Dagger2 入门 2016-12-21 更新:添加@Subcomponent注解以及Lazy与Provider...
    fxzou阅读 28,552评论 77 331
  • 部分内容参考自:[Android]使用Dagger 2依赖注入 - DI介绍(翻译)[Android]使用Dagg...
    AItsuki阅读 47,409评论 66 356
  • ****(说在最前:阅读本篇之前,希望大家对Dagger2已经有了一个初步的了解。从而帮助感觉似是而非的同学进一步...
    我是昵称阅读 913评论 3 6
  • 毕业后进入了图书出版行业,对象主要是高校教,但是总觉得应该还不错,整天和老师打交道。起初的时候很难,我不...
    mi娅的coffee阅读 178评论 0 0
  • 在大学的时候有人向自己告白。关注着我,很久很久,反正当时觉得挺感动的,但是最后还是没有答应.因为我不知道. 一时的...
    怕1噗噗噗噗阅读 239评论 0 0