Dagger2:上手就爱不释手

作者: Leo_Zheng | 来源:发表于2016-12-27 21:28 被阅读1324次

    概览:

    • 什么是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

    相关文章

      网友评论

      • _10_01_:可以使用 annotationProcessor 替换 apt

        ``` Groovy
        dependencies {
        compile 'com.google.dagger:dagger:2.x'
        annotationProcessor 'com.google.dagger:dagger-compiler:2.x'
        }
        ```

        https://github.com/google/dagger
        Leo_Zheng:@Junzz丶 这个和gradle plugin版本号有关,2.2以下还是得用文中的
      • 妙法莲花1234:可以,这个上手有些难度,各种编译失败,熟悉了后确实爱不释手,rxCache 这个库就用dagger 2注入,很多高端玩法😁
        Leo_Zheng:@追风917 哈哈,不过给的错误代码很良心,能一眼看懂
      • 小花野菜:收藏!

      本文标题:Dagger2:上手就爱不释手

      本文链接:https://www.haomeiwen.com/subject/alxsvttx.html