Using Dagger2 in Android

Dagger2是一个Java和Android的依赖注入框架.
本文介绍Android中dagger2的基本使用.
其中包括@Inject, @Component, @Module@Provides注解的使用.

使用依赖注入的好处

1.使用类和被依赖的对象构造分开,这样如果我们需要改变被依赖类的构造方法,不必改动每一个使用类.
2.对各种被依赖类的实例,可以只构造一次.
3.当我们需要更换一种实现时,只需要保证接口一致.
4.利于单元测试,我们可以方便地mock依赖类的对象.

优点总结: 创建对象和使用对象分离, 模块化增强.

Dagger2的使用

Set Up

在项目的build.gradle里加这个:
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'

然后app的build.gradle加这三行:

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

常用注解

最常使用的主要是以下这几个注解:

@Component
Annotates an interface or abstract class for which a fully-formed, dependency-injected implementation is to be generated from a set of modules(). The generated class will have the name of the type annotated with @Component prepended with Dagger. For example, @Component interface MyComponent {...} will produce an implementation named DaggerMyComponent.

@Module
Annotates a class that contributes to the object graph.

@Inject
Dagger constructs instances of your application classes and satisfies their dependencies. It uses the javax.inject.Inject annotation to identify which constructors and fields it is interested in.

Use @Inject to annotate the constructor that Dagger should use to create instances of a class. When a new instance is requested, Dagger will obtain the required parameters values and invoke this constructor.

@Provides

Annotates methods of a module to create a provider method binding. The method's return type is bound to its returned value. The component implementation will pass dependencies to the method as parameters.

Dagger2基本使用

最简单的一个实例

首先写一个Component

@Component(modules = MyApplicationModule.class)
public interface MyApplicationComponent {
    // this should be an interface or abstract class

    // write like this, and Make Project, then a DaggerMyApplicationComponent class will be generated
}

此时里面的Module内容可以暂时为空:

@Module
public class MyApplicationModule {
}

写好后make一下,就生成了

package com.ddmeng.dagger2sample.component;

import com.ddmeng.dagger2sample.module.MyApplicationModule;
import dagger.internal.Preconditions;
import javax.annotation.Generated;

@Generated(
  value = "dagger.internal.codegen.ComponentProcessor",
  comments = "https://google.github.io/dagger"
)
public final class DaggerMyApplicationComponent implements MyApplicationComponent {
  private DaggerMyApplicationComponent(Builder builder) {
    assert builder != null;
  }

  public static Builder builder() {
    return new Builder();
  }

  public static MyApplicationComponent create() {
    return builder().build();
  }

  public static final class Builder {
    private Builder() {}

    public MyApplicationComponent build() {
      return new DaggerMyApplicationComponent(this);
    }

    /**
     * @deprecated This module is declared, but an instance is not used in the component. This method is a no-op. For more, see https://google.github.io/dagger/unused-modules.
     */
    @Deprecated
    public Builder myApplicationModule(MyApplicationModule myApplicationModule) {
      Preconditions.checkNotNull(myApplicationModule);
      return this;
    }
  }
}

需要切换到project视图下才能看见.
生成的这个实现,名字是在我们自己的Component名前面加了Dagger.
如果我们的类名不是顶级的,即还有外部类,则会以下划线分隔连接.

现在我们的生成的myApplicationModule()方法被标记为@Deprecated,这是因为我的module里面什么都还没有呢,所以被认为是没有必要的.

现在我们添加一个要用的LogUtils类. 想要在MainActivity里面用.
写好LogUtils类,在构造函数上标记@Inject. 这时候就将LogUtils加入了dependency graph中, 相当于作为预备队员.

想要在MainActivity作为一个字段用,
在Component里面写一句:
void inject(MainActivity activity);

因为此时还是没有用到Module,所以在application里面可以直接build,保存component:

public class SampleApplication extends Application {

    private MyApplicationComponent component;

    @Override
    public void onCreate() {
        super.onCreate();
        component = DaggerMyApplicationComponent.builder().build();

    }

    public MyApplicationComponent getComponent() {
        return component;
    }
}

在MainActivity使用的时候, 先get到Component, 然后调用inject()方法, 字段就被注入了.

public class MainActivity extends AppCompatActivity {

    @Inject
    LogUtils logUtils;

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

        ((SampleApplication) getApplication()).getComponent().inject(this);
        logUtils.i("tag", "hi, I'm an instance of LogUtils");
    }
}

运行程序后可以看到打出log,证明注入成功.

此时我们看到生成的代码有三个类:

public final class DaggerMyApplicationComponent implements MyApplicationComponent {
public enum LogUtils_Factory implements Factory<LogUtils> {
public final class MainActivity_MembersInjector implements MembersInjector<MainActivity> {

可以通过查看调用栈来看调用关系.

单例@Singleton

如果我们想让工具类是单例,只需要在上面的基础上,在类名前加上@Singleton.

此时对应的Component也需要加上@Singleton.否则编译会不通过.
加好之后,可以打印hashCode()看出, 标记了@Singleton的这个对象,不论被注入几次,都是同一个对象.

在我们的例子中, 可以让FileUtils作为一个单例被注入:

@Singleton
public class FileUtils {

    @Inject
    public FileUtils() {
        Log.i(LogUtils.TAG, "new FileUtils: " + hashCode());
    }

    public void doSomething() {
        Log.i(LogUtils.TAG, "do sth with FileUtils " + hashCode());
    }
}

查看生成的代码,可以看见DaggerMyApplicationComponent为单例的类多保存了一个字段:
private Provider<FileUtils> fileUtilsProvider;

它在init的时候被初始化为:
this.fileUtilsProvider = ScopedProvider.create(FileUtils_Factory.create());

包了一层之后,在ScopeProvider里实现了单例:

  @Override
  public T get() {
    // double-check idiom from EJ2: Item 71
    Object result = instance;
    if (result == UNINITIALIZED) {
      synchronized (this) {
        result = instance;
        if (result == UNINITIALIZED) {
          instance = result = factory.get();
        }
      }
    }
    return (T) result;
  }

@Module@Provides的使用

上面的注入都是用@Inject, 在构造函数和要使用的字段上标记.
有些情况下@Inject是不能满足需求的.

But @Inject doesn’t work everywhere.

  1. Interfaces can’t be constructed. 接口类型不能直接被构造.
  2. Third-party classes can’t be annotated. 第三方的类不能改动它的代码.
  3. Configurable objects must be configured! 需要配置的对象需要被配置.

这些情况下, @Inject不够用啦, 这时候就要用@Provides标记的方法.
方法的返回值返回了它满足的依赖, 它实际返回的对象可以是返回值接口的实现,或者是返回值类型的子类.
@Provides方法也可以有依赖, 即它的参数.
Dagger会注入它的参数值, 如果它的参数值不能被注入, 则编译会失败.
注意这个寻找参数注入的过程是在@Component级别的, 只要这个Component里面有这个参数类型的注入, 即便可能是在另一个Module, 就会自动采用.

所有的@Provides方法都需要放在@Module里面.
按照命名习惯(By convention), 一般@Provides标记的方法都有一个provide前缀, 而module类都有一个Module后缀.
例子:

@Module
public class MyApplicationModule {

    private Context context;

    public MyApplicationModule(Context context) {
        this.context = context;
    }

    @Provides
    @Singleton
    public Context providesContext() {
        return context;
    }

    // Inject interface, return implementation class instance
    @Provides
    public HttpUtil provideHttpUtil() {
        Log.i(LogUtils.TAG, "provideHttpUtil");
        return new MyHttpUtil();
    }

    // Inject class from third-party, or Android framework service
    // This provide method need a parameter, Dagger will obtain the parameter value (injected it)
    // If the parameter is not injectable, then compilation failed
    @Provides
    @Singleton
    ConnectivityManager provideConnectivityManager(Context context) {
        return (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    }
}

Dagger2中几种常用注解总结

@Inject

@Inject的用法分为三种
构造函数上的@Inject:

如果构造函数是有参数的, 则它的所有参数都会自动从dependency graph中找到并注入.
同时构造的这个类也被作为dependency graph的一部分.

但是我们在一个类中最多只能用@Inject标记一个构造方法.

字段上的@Inject: 从dependency graph中找到并注入字段.

这里需要手动调用
(SampleApplication) getApplication()).getComponent().inject(this);
类似的方法, 在这个方法被调用之前, 字段都是null.

注意这里的字段不能是private的.

public方法上的@Inject:

所有方法的参数都会由dependency graph提供.

方法注入在构造函数之后立即调用, 意味着我们可以用一个构建好的this对象.

@Module@Provides

@Module标记了提供依赖的类, 其中包含了一些@Provides标注的方法, 返回值即依赖.

@Component

@Component标记的接口负责将所有的事情联系起来, 可以看做是@Module@Inject之间的桥梁.
我们可以定义我们用的依赖来自哪些Module或者Component.

在Component里可以定义哪些依赖是公有的 (提供返回值为某种依赖的无参数方法) , 也可以定义我们的component可以去哪里inject对象 (void inject()方法, 参数是去注入的地方) .

@Component可以有自己的子Component, 也可以有lifecycle.

先就这么多吧, 更多更高级的使用可以期待下文, 也可以参见后面的参考资料.

本文地址: Using Dagger2 in Android
本文Demo: dagger2-sample

参考资料:

dagger2 repo
dagger2 website
User Guide

Dagger 2.0文档
dagger 2的sample

这里有些guides:
Code Path Guides: DI with dagger2
Dagger2

这里有一系列关于Dagger2的文章还挺好的:

Froger_mcs dev blog
dagger 1 to dagger 2 migration
Introduction to DI
Dagger2 API
Inject everything - ViewHolder and Dagger2 (with Multibinding and AutoFactory example)

Custom Scope

作者的例子:

Github Client

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

推荐阅读更多精彩内容