Dagger2 详解

Dagger2是一个依赖注入框架


什么是依赖注入?

依赖注入,即Dependency Injection,简称DI,又叫做控制反转(Inversion of Control),简称IOC。它的出现是为了降低耦合,降低类与类之间的依赖关系。比如,我们要在一个类A中使用类B,那么我们就需要在类A中去实例化B,这是一般的做法。但是采用依赖注入的方式,我们就可以使用已经实例化好的B直接注入到A中,而不需要在A中去实例化B。


在Studio中引入Dagger2

首先在项目的build.gradle里面添加插件

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.0.0'
        //添加apt插件
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
再app下的build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.3"

    defaultConfig {
        applicationId "com.lzy.dagger2_y"
        minSdkVersion 14
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
添加如下代码,应用apt插件
apply plugin: 'com.neenbedankt.android-apt'
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:23.4.0'

    compile 'com.google.dagger:dagger:2.4'
    apt 'com.google.dagger:dagger-compiler:2.4'
    //java注解
//    compile 'org.glassfish:javax.annotation:10.0-b28'
}

首先从一个简单的使用开始

User类,有两个构造函数,无参数的构造函数用Inject来标注,先不要管inject是什么,后面会说

package com.lzy.dagger2_y;

import javax.inject.Inject;

/**
 * Created by lzy on 2016/10/31.
 */
public class User {
    @Inject
    public User() {
    }

    public User(String name) {
        this.name = name;
    }

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
MainActivity,这里User也用Inject标注

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "lzy";
    
    
    @Inject
    User user;//这里不能申明为private

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

        Log.i(TAG, "onCreate: " + user);
    }
}
此时,我们打印出user,并没有得到预期的效果,发现user为空。所以这里还需要一个桥梁把这两个标注了Inject的联系起来,所以出现了Componet,

创建MainComponent接口,接口里面有个inject方法,方法名是任意取的,传入的参数是MainActivity,这是因为我们要注入的地方就是MainActivity

@Component
public interface MainComponet {
    void inject(MainActivity activity);
}
再在MainActivity中调用以下方法,这时打印出来的user你就会发现并不为空了。

DaggerMainComponet.builder().build().inject(this);

@Inject

从上面可以看出Inject用来在A(MainActivity)中标注所依赖的其他类B(User),而在User类中的构造方法也用User标注,用这个标注使它们联系起来。所以,Inject用在需要需要标注的构造函数上面和用在想要依赖它的类的声明中

@Component

这个注解仿佛就是它们联系的桥梁,在MainActivity中调用了 DaggerMainComponet.builder().build().inject(this)方法,DaggerMainComponet是根据Component标注的接口命名的,然后调用了里面写的方法inject把User注入到MainActivity中,所以Component就像一个注射器一样。


之前我们User类中不是还要一个带参数的构造方法吗,现在我们用@Inject来标注它看看会怎么样

 public User() {
    }
    @Inject
    public User(String name) {
        this.name = name;
    }
这是报了一个错误Error:(10, 10) 错误: java.lang.String cannot be provided without an @Inject constructor or from an @Provides-annotated method.

因为我们的参数name,没有地方传进去,不能这样使用。

当我们如果要使用封装好的类,显然这是不可能去封装好的类的构造函数里面去添加@Inject标注。这样做就要用到Module


@Module

Module就是一个简单的工厂,里面存放的是各种构造方法,创建实例的方法。

前面说到过Component就像一个桥梁,一端是在类中的声明,另一端是类的构造方法,那么现在Module里面放的也是构造方法,那么它俩又会擦出什么样的火花呢?

Component有一个属性叫做modules,通过这个属性可以把相应的Module加入到Component中,从而关联起来,一个Component可以加入多个Module。这样当我们有标注了@Inject的对象需要实例化时就会去到Module中寻找相应的构造方法,在这里面找不到再去找标注了@Inject的构造方法。


@Provides

Provides使用在Module中的,Module中的方法需要用Provides来注解


那么现在写一个Module,假设User是一个封装好的第三方类,无法去内部添加@Inject。在MainModule的构造方法里面传入了一个name,因为只有在这里才能传入参数

@Module
public class MainModule {
    private String mName;

    public MainModule(String name) {
        mName = name;
    }

    @Provides
    User provideUser() {
        return new User(mName);
    }

}
Component,指定属性modules对应MainModule;还有一点注意,不能写返回相同类型的方法,也就是不能再写一个方法名不同,但是都返回User的方法,否则还是会编译不通过。

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

    void inject(MainActivity activity);
}
在MainActivity中注入

DaggerMainComponet.builder().mainModule(new MainModule("哈哈哈哈")).build().inject(this);

看看打印语句,参数已经传入进去,并且先找的是Module里面的构造方法,而不是去找标注了@Inject的构造方法。

所以我们了解到构造依赖有两种方式:在类的构造方法用@Inject标注、在Module中用Provides标注构造方法


现在来梳理上面的使用过程

1.新建一个类User,写构造方法,可以在此处的构造方法添加@Inject标注。如果使用封装好的类,当然直接跳过这部。

2.写一个MainModule类,用@Module来标注这个类,写一个方法 User provideUser()来返回我们需要的User实例,并且用@Provides标注这个方法。当然这里可以有多个方法,也可以写多个Module类。

3.写MainComponent接口,用@Component标注,写一个注射的方法void inject(MainActivity activity),参数就是我们需要注入User的类,这里是MainActivity。

4.rebuild一下项目,在MainActivity声明的User上面添加@Inject标注,调用DaggerMainComponet.builder().mainModule(new MainModule("哈哈哈哈")).build().inject(this)


@Singleton和@Scope

现在在MainActivity中新增一个User对象,打印出两个user,发现它们的内存地址并不一样

那么现在在下面在Module和Component中添加@Singleton注解,如下所示:

@Module
public class MainModule {
    private String mName;

    public MainModule(String name) {
        mName = name;
    }

    @Singleton
    @Provides
    User provideUser() {
        return new User(mName);
    }

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

    User provideUser();//这里不能传入参数,否则报错

    void inject(MainActivity activity);
}
再次打印出来,发现内存地址一样。因为Singleton代表单例,所以内存地址都一样


使用@Singleton注解是一个全局的单例模式,如果我们想要局部的单例该怎么办,比如现在有三个Activity(MainActivity、AActivity、BActivity)需要注入User,而要求MainActivity和AActivity的user内存地址是相同的,而BActivity不同。这样就需要用到@Scope

@Scope

Scope代表着作用域,也就是作用的范围。

现在要实现上面说到的效果,首先自定义一个Scope,如下:

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface UserScope {
}
和添加@Singleton同理在Component和Module添加自定义的注解

@Module
public class MainModule {

    public MainModule() {
    }

    @UserScope
    @Provides
    User provideUser() {
        return new User();
    }

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

    User provideUser();//这里不能传入参数,否则报错

    void inject(MainActivity activity);

    void inject(AActivity activity);
}
这里添加了一个注入AActivity的方法,因为要在里面注入User

接着自定义Application,在这里面获得Component,如下所示:

public class MyApplication extends Application {

    private static MainComponent component;

    @Override
    public void onCreate() {
        super.onCreate();
        component = DaggerMainComponent.builder().mainModule(new MainModule()).build();
    }

    public static MainComponent getComponent() {
        return component;
    }
}
当然别忘了再manifest里面配置MyApplication

MainActivity

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "lzy";

    @Inject
    User user;//这里不能申明为private

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

        MyApplication.getComponent().inject(this);
        Log.i(TAG, "MainActivity: " + user);
    }
    //跳转界面
    public void jump(View view) {
        startActivity(new Intent(this, AActivity.class));
    }
}
AAcitivity

public class AActivity extends AppCompatActivity {

    @Inject
    User user;

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

        MyApplication.getComponent().inject(this);
        Log.i("lzy", "AActivity: " + user);
    }

    public void jump(View view) {
        startActivity(new Intent(this, BActivity.class));
    }
}

运行打印日志,可以看到是同一个内存地址。

其实这个Singleton也并不能创建单例,它和我们自定义的Scope是一样的,并没有什么不同,从上面可以看出是在Application中创建了MainComponent,只有一个实例,用来管理Module。但是到底为什么加上了@Singleton就变成单例呢

在编译时在会自动在build目录下生产辅助类,由于篇幅问题具体就不多说了,想了解的可以自己去看看,具体在build/generated/source/apt/debug下面。就在测试发现当加上Scope时,发现生成的DaggerXXComponent类,其中获取Provider<T>时,加了一个DoubleCheck.provider方法,然而不加Scope时并不会有这一步,这个方法就是判断是否有缓存,有的话就直接返回之前的,所以获取的内存地址是一样的,实现了单例。


@Qualifier和@Named

限定符,之前不是说不能再Module里面出现相同返回值类型的对象嘛,有了qualifier就可以了,它的出现是为了区分对象不同的实例。

看看具体的 使用,首先要自定义一个Qualifier

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface UserQualifier {
    String value() default "";
}

Module,两个返回User的方法,用UserQualifier标注,用a,b区分

@Module
public class MainModule {

    public MainModule() {
    }

    @UserQualifier("a")
    @UserScope
    @Provides
    User provideUser1() {
        return new User();
    }

    @UserQualifier("b")
    @UserScope
    @Provides
    User provideUser2() {
        return new User();
    }

}
主界面声明使用

 @UserQualifier("a")
    @Inject
    User user;//这里不能申明为private

    @UserQualifier("b")
    @Inject
    User user1;

这样打印出来两个user,它们的内存地址就不同了。@Named也是这样定义的,所以可以直接使用@Named也一样。


基本使用差不多介绍完毕。感兴趣的朋友可以继续看下一篇的使用例子的详细介绍http://blog.csdn.net/lylodyf/article/details/53009042


官方介绍地址http://google.github.io/dagger/






评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值