Android:Dagger2学习之由浅入深

概述

Dagger2是一款使用在Java和Android上的静态的,运行时依赖注入框架.官方地址:http://google.github.io/dagger/

记得当初刚学习Dagger2的时候看了许多博客,但是感觉上手依然困难,所谓光学不练就是这个意思吧

时至今日,用上此框架的同仁越来越多.分析文章也很多,上手相对要简单了许多.

学习Dagger2最先要明白的是其各个注解的含义及工作原理,这样才可以快速的上手和使用.

在这里简要记录一下在使用Dagger2过程中的感受和心得体会.

本文示例代码地址:Dagger2Sample

配置信息

首先贴出此篇博客的所有依赖配置信息,因为dagger2需要依赖 apt,所以也需要引入apt插件,

  • project 下面的build.gradle文件配置
buildscript {
  repositories {
    jcenter()
    //依赖maven库
    mavenCentral()
  }
  dependencies {
    classpath 'com.android.tools.build:gradle:2.1.2'

    // NOTE: Do not place your application dependencies here; they belong
    // in the individual module build.gradle files
    classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    classpath 'me.tatarka:gradle-retrolambda:3.2.5'
  }
}

allprojects {
  repositories {
    jcenter()
    mavenCentral()
  }
}
  • app目录下的build.gradle文件配置
apply plugin: 'me.tatarka.retrolambda' //使用lanbda表达式
apply plugin: 'com.neenbedankt.android-apt' //apt插件

android {
  //...
  // 注释冲突
  packagingOptions {
    exclude 'META-INF/services/javax.annotation.processing.Processor'
  }

  // 使用Java1.8
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
}

dependencies {
  compile fileTree(dir: 'libs', include: ['*.jar'])
  testCompile 'junit:junit:4.12'
  compile 'com.android.support:appcompat-v7:23.4.0'

  apt 'com.google.dagger:dagger-compiler:2.0.2'
  compile 'com.google.dagger:dagger:2.0.2'
  provided 'javax.annotation:jsr250-api:1.0'

  compile 'io.reactivex:rxandroid:1.1.0' // RxAndroid
  compile 'io.reactivex:rxjava:1.1.0' // RxJava

  compile 'com.squareup.retrofit2:retrofit:2.0.2' // Retrofit网络处理
  compile 'com.squareup.retrofit2:adapter-rxjava:2.0.2' // Retrofit的rx解析库
  compile 'com.squareup.retrofit2:converter-gson:2.0.2' // Retrofit的gson库

  compile 'com.jakewharton:butterknife:8.0.1' // 标注
  apt 'com.jakewharton:butterknife-compiler:8.0.1' //视图注入

}

Dagger2基础注解

Inject,Component,Module,Provides是Dagger中最基础的几个注解,是整个依赖注入的核心,下面我们来看一下各个注解的作用.

Inject and Component

@Inject:

用来标注需要依赖的成员被依赖类的构造函数(如果依赖类同时依赖了其他类,其他类的构造函数也要有@Inject标注),

注意: 使用@Inject标注构造函数,不能标注一个类的多个构造函数


  @Inject public UserModel() {
    this.name = "Hello Dagger2,I`m from Inject";
  }
  @Inject public UserModel(String name) {
    this.name = name;
  }

上面的写法会报错:Error:(29, 18) 错误: Types may only contain one @Inject constructor.

@Component:

光有@Inject可不行,还需要一个东西将他两联系起来才能让依赖类找到被依赖对象,其中Component就起到了这个作用,在Dagger2中是以接口形式存在,
是用来连接 需要依赖类被依赖类,Component使用injectXX(XX xx)将依赖注入到需要依赖的地方,

基本使用

接下来我们来看一下Dagger2最基本的用法

第一步:编写JavaBean

/**
 * Created on 16/6/8.下午8:57.
 *
 * @author bobomee
 */
public class UserModel {
  private String name;

  @Inject public UserModel() {
    this.name = "Hello Dagger2,I`m from Inject";
  }

  public String getName() {
    return name;
  }
}

第二步:创建Component

/**
 * Created on 16/6/8.下午8:59.
 *
 * @author bobomee
 */
@Component public interface UserComponent {

  void inject(MainActivity mainActivity);

  final class Initializer {
    private Initializer() {
    }

    public static UserComponent init() {
      return DaggerUserComponent.create();
    }
  }
}

第三步:构建依赖,先build一下生成dagger图谱

public class MainActivity extends AppCompatActivity {

  @BindView(R.id.text) TextView text;

  @Inject UserModel userModel;

  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ButterKnife.bind(this);

    //inject dependencies
    UserComponent.Initializer.init().inject(this);

    text.setText(userModel.getName());
  }
}

注意: @Inject成员不能是private的,否则会报:Error:(35, 29) 错误: Dagger does not support injection into private fields

Provides and Module

@Provides:

自定义依赖,Dagger2中不仅提供了@Inject标注,他比@Inject更加强大,不仅可以提供本地依赖,还可以提供第三方依赖(第三方库和Android系统类不可以用@Inject注解构造函数).

注意: @Provides的优先级高于@Inject

@Module:

所有的@Provides都必须包含在@Module内部,相当于简单工厂,提供了各种依赖@Provides方法.之后再将Module加入到Component管理即可完成依赖注入(Component不仅可以从@Inject找到被依赖类,还可以从@Module找到被依赖类)

注意: 通过modules列出一个Component所有依赖的Module,如果缺失任何一个编译会报错

自定义Module使用

第一步:编写AppModule,提供依赖@Provide方法,其中@Singleton是自定义注解

/**
 * Created on 16/6/8.下午9:30.
 *
 * @author bobomee
 */
@Module public class AppModule {

  private App app;//App为我们自定义的Application

  public AppModule(App app) {
    this.app = app;
  }

  @Provides @Singleton public App provideApp() {
    return app;
  }
}

第二步:创建Component,这里使用了@Singleton,必须和Module中@Provides方法修饰相同,否则编译报错

/**
 * Created on 16/6/8.下午9:31.
 *
 * @author bobomee
 */
@Singleton @Component(modules = {
    AppModule.class
}) public interface AppComponent {

//将依赖注入到自定义的Application
  void inject(App app);

  final class Initializer {
    private Initializer() {
    }

    public static AppComponent init(App app) {
      return DaggerAppComponent.builder().appModule(new AppModule(app)).build();
    }
  }
}

第三步:自定义Application中完成依赖

/**
 * Created on 16/6/8.下午9:29.
 *
 * @author bobomee
 */
public class App extends Application {

  private AppComponent appComponent;

  @Inject static App app;

  public static App get() {
    return app;
  }

  @Override public void onCreate() {
    super.onCreate();

//当inject完成后app就不为空了,且和Application生命周期相同,因此//Singleton可以起到全局单例的作用
    buildComponent();
  }

  private void buildComponent() {
    appComponent = AppComponent.Initializer.init(this);
    appComponent.inject(this);
  }

//向外提供appComponent,方便其他依赖appComponent的component构建
  public AppComponent component() {
    return appComponent;
  }
}

第四步:使用依赖

//MainActivity中
 @BindView(R.id.text1) TextView text1;

 text1.setText(App.get().toString());

Component组织方式

  1. 一个应用中,必须包含一个全局的Component(类似于上面的AppComponent),管理整个App的实例.

  2. 一般AppComponent和Application生命周期相同,所以注入到Application中即可.

  3. 因为Application是全局单例的,所以AppModule中创建的实例也是单例的(@Singleton注解就是依照此原理).

  4. 根据具体功能或者单独的一个页面定义一个Component.

  5. 某个单独的Component要用到全局实例的时候,可以通过继承AppComponent来实现

  6. Component之间的关系有 依赖(dependencies),包含(SubComponent),继承方式(extends)

Component依赖写法

接下来来看一下一个典型的dependencies写法

第一步: 定义注入到MainActivity的Product

/**
 * Created on 16/6/8.下午9:48.
 *
 * @author bobomee
 */
public class Product {

  private String productQualifier;

  public Product(String productQualifier) {
    this.productQualifier = productQualifier;
  }

  public String getProductQualifier() {
    return productQualifier;
  }
}

第二步:定义Component

/**
 * Created on 16/6/8.下午8:59.
 *
 * @author bobomee
 */
@Component public interface UserComponent {

  // unused
  //void inject(MainActivity mainActivity);

  final class Initializer {
    private Initializer() {
    }

    public static UserComponent init() {
      return DaggerUserComponent.create();
    }
  }
}
/**
 * Created on 16/6/8.下午10:00.
 *
 * @author bobomee
 */
@ActivityScope @Component(dependencies = UserComponent.class,
    modules = ProductModule.class) public interface ProductComponent extends UserComponent {

  void inject(MainActivity mainActivity);

  final class Initializer {
    private Initializer() {
    }

    public static ProductComponent init(UserComponent userComponent) {
      //dependency usercomponent
      return DaggerProductComponent.builder().productModule(new ProductModule()).userComponent(userComponent).build();
    }
  }
}

注意: 此处依赖UserComponent,需要将UserComponent中的注入删除.

//MainActivity
//inject dependencies
ProductComponent.Initializer.init(UserComponent.Initializer.init()).inject(this);

@Scope:

注解作用域.用于更好的组织Component,和定义Component的粒度.
@Singleton是Dagger2默认实现的,用于管理全局单例(AppComponent中),
@Scope用在Component 和 Module 头上,
如果要定义一个实例的生命周期在Activity内,则可以定义@ActivityScope(当然名称随便起,只要对应即可)

/**
 * Created on 16/6/8.下午9:55.
 *
 * @author bobomee
 */
@Scope @Retention(RetentionPolicy.RUNTIME) public @interface ActivityScope {
}
/**
 * Created on 16/6/8.下午10:00.
 *
 * @author bobomee
 */
@ActivityScope @Component(dependencies = UserComponent.class,
    modules = ProductModule.class) public interface ProductComponent extends UserComponent {
//...
}
/**
 * Created on 16/6/8.下午9:49.
 *
 * @author bobomee
 */
@Module public class ProductModule {

  @ActivityScope @Provides Product provideProduct() {
    return new Product("this is a product");
  }
//...
}

依赖迷失之Qualifier

@Qualifier:

限定符,当依赖类中需要被依赖类的两个不同对象的时候,帮助我们去为相同接口的依赖创建“tags”.
如我们需要两个不同的level的product,Qualifier会帮助你区分对应的一个,

/**
 * Created on 16/6/8.下午9:57.
 *
 * @author bobomee
 */
@Qualifier @Retention(RetentionPolicy.RUNTIME) public @interface ProductLevel {
  String value() default "";
}
  • 声明Module
/**
 * Created on 16/6/8.下午9:49.
 *
 * @author bobomee
 */
@Module public class ProductModule {

//....
  @ActivityScope @ProductLevel("good") @Provides Product provideGoodProduct() {
    return new Product("good product");
  }

  @ActivityScope @ProductLevel("bad") @Provides Product provideBadProduct() {
    return new Product("bad product");
  }
}
  • 注入Inject
@Inject @ProductLevel("good") Product product1;
@Inject @ProductLevel("bad") Product product2;

总结

  • Inject用来标注依赖被依赖的构造函数
  • Provides提供依赖的方法上添加的注解,provide方法需要包含在Module中
  • Module专门提供依赖,类似工厂模式,包含Provides方法
  • Component依赖被依赖的桥梁,(先从Module中找依赖,再从Inject构造函数找)
  • Scope自定义注解,用于标示作用域,命名随意,对应即可,其中@Singleton是一个系统的模式实现.(管理Module与Component的匹配)
  • Qualifier自定义注解,用于解决一个实例可以被多种方式构建的依赖迷失问题
  • Component有三种组织关系,分为依赖,包含和继承,用于解决依赖复用与共享问题

依赖注入的过程:
步骤1:查找Module中是否存在创建该类的方法。

步骤2:若存在创建类方法,查看该方法是否存在参数

...........步骤2.1:若存在参数,则按从步骤1开始依次初始化每个参数

...........步骤2.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束

步骤3:若不存在创建类方法,则查找Inject注解的构造函数,看构造函数是否存在参数

...........步骤3.1:若存在参数,则从步骤1开始依次初始化每个参数

...........步骤3.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束

参考:

这里推荐牛晓伟的三篇博客:

Android:dagger2让你爱不释手-基础依赖注入框架篇

Android:dagger2让你爱不释手-重点概念讲解、融合篇

Android:dagger2让你爱不释手-终结篇

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

推荐阅读更多精彩内容