Dagger2入门详解

Dagger2入门详解

参考文章

Dagger官网

Dagger Document API

从零开始的Android新项目4

http://www.bozhiyue.com/anroid/boke/2016/0719/273761.html


环境配置

这里以Gradle配置为例子,实用得是AndroidStudio:

  1. 打开project 的 build.gradle ,添加
dependencies {
        classpath 'com.android.tools.build:gradle:2.2.2'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
        
        //dagger2
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
  1. 打开 app module 的 build.gradle,添加
apply plugin: 'com.android.application'
//dagger2
apply plugin: 'com.neenbedankt.android-apt'

//...

dependencies {
    //...

    //dagger2
    compile 'com.google.dagger:dagger:2.4'
    apt 'com.google.dagger:dagger-compiler:2.4'
    compile 'org.glassfish:javax.annotation:10.0-b28'
}
  1. 然后sync gradle一下,环境就配置好了,在实用dagger2的时候,会自动生成一些类,所以最好记一下 build project的快捷键 ctrl+F9,写好dagger代码,然后build一下,就会自动生成 DaggerXXX 开头的一些类。

入门实例

好了,下面我们来看一个入门实例,实用Dagger2到底是怎么依赖注入的。

现在又一个Person类(这里为了简单起见),然后MainActivity中又一个成员变量person。

Person.java

public class Person {

    public Person() {
        System.out.println("a person created");
    }
}

MainActivity.java

public class MainActivity extends AppCompatActivity {
   
    Person person;

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

        person = new Person();
    }
}

如果不适用依赖注入,那么我们只能在MainActivity中自己new一个Person对象,然后使用。

使用依赖注入:

public class MainActivity extends AppCompatActivity {

    @Inject
    Person person;

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

在Person对象上添加一个 @Inject注解,即可自动注入对象。

那么问题来了,就一个@Inject 注解,系统就会自动给我创建一个对象? 当然不是,这个时候我们需要一个Person类的提供者。估计叫它: MainModule

MainModule.java

@Module
public class MainModule {

    @Provides
    Person providesPerson() {
        System.out.println("a person created from MainModule");
        return new Person();
    }
}

里面两个注解,@Module@Provides,Module标注的对象,你可以把它想象成一个工厂,可以向外提供一些类的对象。那么到底提供什么对象呢?

@Provides标注的方法就是提供对象的,这种方法一般会返回一个对象实例,例如上面返回一个 Person对象

那么好了,现在Perso类的提供者也有了,我们是不是可以运行起来了。ctrol+F9 build一下项目,然后运行。发现没有任何输出(如果创建Person对象,会打印消息)。为什么了?

这个时候需要引入第3个东东,component容器。可以把它想成一个容器, module中产出的东西都放在里面,然后将component与我要注入的MainActivity做关联,MainActivity中需要的person就可以冲 component中去去取出来。

MainComponent.java

@Component(modules = {MainModule.class})
public interface MainComponent {

    void inject(MainActivity mainActivity);
}

看到一个新注入 @Component 表示这个接口是一个容器,并且与 MainModule.class 关联,它生产的东西都在这里。
void inject(MainActivity mainActivity); 表示我怎么和要注入的类关联。这个比较抽象!这个时候我们可以 build 一下项目。

然后在MainActivity中将component 关联进去:

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

        MainComponent component = DaggerMainComponent.builder()
                .mainModule(new MainModule()).build();
        component.inject(this);
    }

下面就是将 MainActivity和Module通过Component关联起来的代码,那么这个时候系统看到 有一个 @Inject修饰的Person,就知道在这个 MainComponent中去找,并且是有 MainModule 的 Provides修饰的方法提供的。

MainComponent component = DaggerMainComponent.builder()
    .mainModule(new MainModule()).build();
component.inject(this);

然后 build 项目,运行项目,发现打印:

person from module
a person created

说明确实系统创建了对象,并且注入到MainActivity中。

细心的同学会发现,MainComponent, MainModule, MainActivity 都是我们自己创建的,上面还有一个 DaggerMainComponent 是上面鬼?这就是你 build project 的时候,dagger自己为你生成的具体的component类(我们自己定义的是MainComponent接口)。感兴趣的可以直接跟到代码里面去看看。

好了,上面我们把 DI (Dependency Inject) 最基本的流程走了一遍,用到了几个注解:

  1. @Inject
  2. @Module
  3. @Component
  4. @Provides

下面来介绍另外几个常用的注解。

其他注解和情况

如果只有最简单的情况,那么上面的几个注解已经够了,但是其实还有很多情形,我们稍微展示几个

单例模式 @Singleton(基于Component)

基于Component的单例模式,怎么理解呢?也是是 在这个Component 对象中,一个对象是单例对象。如果又新创建了一个Component,那么两个Component中的当你对象是不一样的。具体的看后面介绍。

上面的MainActivity代码不变,我们再在MainActivity中添加一个 @Inejct Person person2,并打印两个 person对象,结果如下:

person from module
a person created
person from module
a person created
org.yxm.daggerlearn2.data.Person@64cf4a2
org.yxm.daggerlearn2.data.Person@899b533

发现person会被创建两次,并且两个person对象也不同,如果我们希望只有一个 person 和 person2 都指向同一个Person对象了? 使用 @Singleton 注解

两个地方需要加:

  1. MainModule.java 的 provides方法上需要添加 @Singleton 注解
@Module
public class MainModule {

    private static final String TAG = "MainModule";

    @Singleton
    @Provides
    public Person providesPerson() {
        Log.d(TAG, "person from module");
        return new Person();
    }
}
  1. MainComponent.java 类上添加
@Singleton
@Component(modules = {MainModule.class})
public interface MainComponent {

    void inject(MainActivity mainActivity);
}

再运行,发现只创建了一次,并且两个person指向同一个对象。

person from module
a person created
org.yxm.daggerlearn2.data.Person@64cf4a2
org.yxm.daggerlearn2.data.Person@64cf4a2

需要非常注意的是:单例是基于Component的,所以不仅 Provides 的地方要加 @Singleton,Component上也需要加。并且如果有另外一个OtherActivity,并且创建了一个MainComponent,也注入Person,这个时候 MainActivity和OtherActivity中的Person是不构成单例的,因为它们的Component是不同的。

带有参数的依赖对象

如果构造Person类,需要一个参数Context,我们怎么注入呢? 要知道注入的时候我们只有一个 @Inject 注解,并不能带参数。所以我们需要再 MainModule 中提供context,并且由 providesXXX 函数自己去构造。如:

Person.java

public class Person {

    private Context context;

    public Person(Context context) {
        Log.d(TAG, "a person created with context:"+context);
    }
}

修改MainModule.java

@Module
public class MainModule {

    private static final String TAG = "MainModule";

    private Context context;

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

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

    @Singleton
    @Provides
    public Person providesPerson(Context context) {
        Log.d(TAG, "person from module");
        return new Person(context);
    }
}

这里需要强调的是, providesPerson(Context context)中的 context,不能直接使用 成员变量 this.context,而是要在本类中提供一个 Context providesContext()@Provides 方法,这样在发现需要 context 的时候会调用 provideContext 来获取,这也是为了解耦。

依赖一个组件

如果组件之间有依赖,比如 Activity 依赖 Application一样,Application中的东西,Activity要直接可以注入,怎么实现呢?

例如,现在由 AppModule 提供Context对象, ActivityModule 自己无需提供Context对象,而只需要依赖于 AppModule,然后获取Context 对象即可。

AppModule.java

@Module
public class AppModule {

    private Context context;

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

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

AppComponent.java

@Component(modules = {AppModule.class})
public interface AppComponent {
    // 向下层提供Context
    Context getContext();
}

ActivityModule.java

@Module
public class ActivityModule {

    @Provides
    Person providePerson(Context context) {
        return new Person(context);
    }
}

ActivityComponent.java

@Component(dependencies = {AppComponent.class}, modules = {ActivityModule.class})
public interface ActivityComponent {

    void inject(MainActivity mainActivity);
}

通过上面例子,我们需要注意:

  1. ActivityModule 也需要创建Person时的Context对象,但是本类中却没有 providesContext() 的方法,因为它通过 ActivityComponent依赖于 AppComponent,所以可以通过 AppComponent中的 providesContext() 方法获取到Context对象。
  2. AppComponent中必须提供 Context getContext(); 这样返回值是 Context 对象的方法接口,否则ActivityModule中无法获取。

使用方法:一定要在 activityComponent中注入 appComponent 这个它依赖的组件。我们可以看到,由于AppComponent没有直接和 MainActivity发生关系,所以它没有 void inject(...);这样的接口

AppComponent appComponent = DaggerAppComponent.builder()
        .appModule(new AppModule(this))
        .build();
ActivityComponent activityComponent = DaggerActivityComponent.builder()
        .appComponent(appComponent)
        .activityModule(new ActivityModule())
        .build();
activityComponent.inject(this);

自定义标记 @Qualifier 和 @Named

如果Person中有两个构造方法,那么在依赖注入的时候,它怎么知道我该调用哪个构造方法呢?

修改Person类,两个不同的构造方法
Person.java

public class Person {

    private static final String TAG = "Person";

    private Context context;
    private String name;

    public Person(Context context) {
        Log.d(TAG, "a person created with context:" + context);
    }

    public Person(String name) {
        this.name = name;
        Log.d(TAG, "a person created with name:" + name);
    }
}

有两种方法可以解决这个问题:

@Named("...")

@Module
public class MainModule {

    private static final String TAG = "MainModule";

    private Context context;

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

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

    @Named("context")
    @Provides
    public Person providesPersonWithContext(Context context) {
        return new Person(context);
    }


    @Named("string")
    @Provides
    public Person providesPersonWithName() {
        return new Person("yxm");
    }
}

分别在两个提供Person的provides方法上添加 @Named标签,并指定。

然后在要依赖注入的地方,同样添加 @Name 标注表示要注入时使用哪一种

    @Named("string")
    @Inject
    Person p1;

    @Named("context")
    @Inject
    Person p2;

@Qualifier自定义标签

使用@Named 会使用到 字符串 ,如果两边都必须写对才能成功,并且字符串总是不那么优雅的,容易出错,所以我们可以自定义标签来解决上面的问题。

PersonWithContext.java

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

PersonWithName.java

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

这样就自定义了两个标签,然后在刚刚所有使用到 @Named 的地方替换成我们自定义的标签即可:

MainModule.java

    // ...
    @PersonWithContext
    @Provides
    public Person providesPersonWithContext(Context context) {
        return new Person(context);
    }


    @PersonWithName
    @Provides
    public Person providesPersonWithName() {
        return new Person("yxm");
    }

依赖注入的地方:

    @PersonWithContext
    @Inject
    Person p1;

    @PersonWithContext
    @Inject
    Person p2;

输入:调用了两种构造方法

a person created with context:org.yxm.daggerlearn2.MainActivity@8018a6e
a person created with name:yxm

懒加载Lazy和强制重新加载Provider

在注入时分别使用 Lazy 和 Provider 修饰要注入的对象:

@Inject
Lazy<Person> lazyPerson;

@Inject
Provider<Person> providerPerson;

在使用的地方,用 .get()方法获取:

Person p1 = lazyPerson.get();
Person p2 = lazyPerson.get();
Person p3 = providerPerson.get();
Person p4 = providerPerson.get();

打印结果:

a person created with context:org.yxm.daggerlearn2.MainActivity@8018a6e
a person created with name:yxm
a person created with name:yxm

说明 lazyPerson 多次get 的是同一个对象,providerPerson多次get,每次get都会尝试创建新的对象。

@Scope 自定义生命周期

通过前面的例子,我们遇到了 @Singleton 这个标签,它可以保证在同一个Component中,一个对象是单例对象。其实可以跟进去看代码:

Singleton.java

@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}

就这么多东西。利用单例和组件间依赖的关系,是不是我们也可以定义生命周期来满足我们的需求呢?比如 Application,Activity 这样的生命周期。下面我们来创建这两个生命周期:

Application生命周期
ApplicationScope.java

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

Activity生命周期
ActivityScope.java

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

除了名字,其他都和 @Singleton 是一样的。

然后用 ApplicationScope 来修饰 AppModule和AppComponent,ActivityScope 修饰 ActivityModule和ActivityComponent
AppModule.java

@Module
public class AppModule {

    private Context context;

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

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

AppComponent.java

@ApplicationScope
@Component(modules = {AppModule.class})
public interface AppComponent {

    // 向下层提供Context
    Context getContext();

}

ActivityModule.java

@Module
public class ActivityModule {

    @ActivityScope
    @PersonWithContext
    @Provides
    Person providesPersonWithContext(Context context) {
        return new Person(context);
    }

    @ActivityScope
    @PersonWithName
    @Provides
    Person providesPersonWithString() {
        return new Person("yxm");
    }
}

ActivityComponent.java

@ActivityScope
@Component(dependencies = {AppComponent.class}, modules = {ActivityModule.class})
public interface ActivityComponent {

    void inject(MainActivity mainActivity);
}

然后创建自定义Application:
App.java

public class App extends Application {

    public static AppComponent appComponent;

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

        appComponent = DaggerAppComponent.builder()
                .appModule(new AppModule(this))
                .build();
    }
}

在MainActivity中注入:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    @PersonWithContext
    @Inject
    Person p1;

    @PersonWithContext
    @Inject
    Person p2;

    @PersonWithName
    @Inject
    Person p3;

    @PersonWithName
    @Inject
    Person p4;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        ActivityComponent activityComponent = DaggerActivityComponent.builder()
                .appComponent(App.appComponent)
                .activityModule(new ActivityModule())
                .build();
        activityComponent.inject(this);

        Log.d(TAG, "" + p1);
        Log.d(TAG, "" + p2);
        Log.d(TAG, "" + p3);
        Log.d(TAG, "" + p4);

    }
}

可以看到我们注入了4个Person对象,打印结果:

a person created with context:org.yxm.daggerlearn2.App@103efff
a person created with name:yxm
org.yxm.daggerlearn2.data.Person@711cc
org.yxm.daggerlearn2.data.Person@711cc
org.yxm.daggerlearn2.data.Person@a36ec15
org.yxm.daggerlearn2.data.Person@a36ec15

只创建了两次,说明在ActivityScope生命周期中,创建了两种 Person,并且它们保持各自的单例。

其实看到这里,再说单例我都觉得不是很准确了,它其实就是说的在这个Component中可以又几个这样的对象。如果是普通的就可能有很多个,如果是Scope修饰的,就只有一个。

总结

哎哟我去,还是写了很长,这篇还是主要展示了Dagger2中最基本的环境配置,和常用的标签以及特性,列举一下:

  1. @Inject
  2. @Module
  3. @Component
  4. @Singleton
  5. @Scope
  6. @Named, @Qualifier
  7. Lazy, Provider
  8. modules, dependencys

然后也踩了一些坑,很无知的错误:

  1. 在Component中我们会使用:void inject(实体类),表示这个Component 可以与实体类关联,然后为它注入。注意这里的实体类必须是直接关联的那个类,如果你填它的父类,按照多态你也可以在 compoment.inject(...)的时候成功,但是却无法注入 。纠结了好一会儿。
  2. 一旦使用 compoment.inject(...) 使某个实体类和Component发生了关系,那么对应Component的 Module 中必须提供 @Inject修饰的所有对象的 providesXXX 方法,而且如果有两种构造方法,必须提供两种 providesXXX 方法哦!又被坑了好一会儿。
  3. 如果被依赖的 Component 使用了Scope,那么依赖他的 Component 也必须使用Scope才能使用。典型例子就是:AppComponent使用了 Scope,那么ActivityComponent也必须使用Scope,否则会编译出错。

下一篇来介绍Android中怎么实际使用Dagger2来达到解耦的目的,敬请期待吧

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

推荐阅读更多精彩内容