【android】摆正姿势,dagger2原来如此简单

1.前言

依稀记得两年之前就偷偷学习过dagger2的使用,当然那个时候也是云里雾里,对照着博客确实实现了对象的注入,但是后来基本上没有再深入的学习过,虽然偶尔也会浏览到dagger2的文章,但是实际项目中并没有使用,一直搁置着,最近突然脑子发热,想学习了,刚好学到了这里,趁着脑子热,索性就记录下,待以后需要使用的翻看便知。

2.什么是dagger2

dagger2是一款非常有名的依赖注入框架,那么问题来了,什么是依赖注入,很简单,就是字面意思,当你需要创建对象的时候,依赖于其他的程序进行注入,而不是自己主动去创建。

平时我们用到的set方法其实也是一种依赖注入的方式,再比如有参构造函数传递过来的对象同样属于注入的一种。

优点:

  • 避免无用的体力劳动(重复多次new对象)
  • 更好的管理类的实例,避免耦合
引入dagger2:
    annotationProcessor 'com.google.dagger:dagger-compiler:2.11' 
    implementation 'com.google.dagger:dagger-android:2.11'
    implementation 'com.google.dagger:dagger-android-support:2.11'
    // if you use the support libraries
    annotationProcessor 'com.google.dagger:dagger-android-processor:2.11'

3.最简单的对象注入(无参)

dagger2的使用依赖于几个比较主要的注解,下面跟随我一切来实现一个简单的对象注入,我们想要在MainActivity创建一个School对象:

1.首先创建School类

public class School {
    @Inject
    public School() {
    }
}

可以看到我们在School的构造函数上加上了@Inject注解,此处的作用就是 :

标识School类可以被注入,相对于给注入的对象指定一个创建的来源,不然程序无法知晓如果创建school对象。

2.school类有了,也标明了它可以被注入(使用@Inject注解),下面我们就要找个东西来创建这个实例,@Component注解就是这个作用,它相当于一个注入器,负责生产类的实例,起到一个桥梁的作用,一端是生产,一端是注入。

@Component
public interface MainComponent {
    void inject(MainActivity activity);
}

3.Android Studio中 build---> make project,或者单独编译下module也可以,编译完成后将会自动生成dagger2注入所需的代码,代码生成位置在:


2.png

此时我们开始把school对象注入到MainActivity中,代码如下:

public class MainActivity extends AppCompatActivity {

    @Inject
    School school;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //inject方法负责连接所创建的实例和mainActivity,此时实例被赋值给school变量
        DaggerMainComponent.create().inject(this);
        Log.e("daggerLog","school :【"+school+"】");
    }
}

此时运行程序,就会出现下面的打印结果:

1.png

我们发现,MainActivity并没有new School()的操作,仅仅通过@Inject注解,并且进行inject连接之后,对象就“自动”产生了。

有些杠精可能要说了,这TM实在逗我,创建个对象一行代码搞定了,你给我弄这么多花里胡哨的不嫌麻烦吗?我只能说你说的没毛病,单个对象不频繁的创建或许并不需要这么麻烦,但是为了降低代码的耦合性,或者说从架构方面考虑,可能这真的很有必要。

4.有参的对象注入

上面我们只是简单定义了一个无参的School对象,那么有参的对象应该如何注入呢,下面我们改造下School类。

public class School {

    private String name;

    private String address;

    @Inject
    public School(String name, String address) {
        this.name = name;
        this.address = address;
    }
}

嗯,也没干啥,就增加了两个参数,我们来编译一下:

3.png

完犊子,报错了,看错误提示里面出现了@Provides注解,那么对于有参的构造函数,该如何进行注入呢,dagger2给我们提供了两个注解,@Module和@Provides,
@Module标识我是用来提供注入实例的,@Provides用来表示具体提供实例的方法,看下面的这段代码:

@Module
public class MainModules {

    @Provides
    public School provideSchool(){
        return new School("南京高级中学","江苏南京");
    }
}

在class名字上方加上@Module注解,在具体提供实例的地方添加上@Provides注解,此时School就具备了被注入的能力,但前面我们说到连接实例和被注入端使用的是@Component注解,那么如何为@Component指定使用哪个Module注入呢?

@Component(modules = MainModules.class)
public interface MainComponent {
    void inject(MainActivity activity);
}

很简单,通过@Component中的modules关键字来指定提供实例的module,如果有多个module可以使用{}括起来,形如下面这样的形式:

@Component(modules = {MainModules.class,OtherModules.class})

此时我们再次编译项目,并且修改MainActivity代码如下:

public class MainActivity extends AppCompatActivity {

    @Inject
    School school;

    @Inject
    School school2;

    private Button btnJump;

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

        DaggerMainComponent.create().inject(this);

        Log.e("daggerLog","school name:【"+school.getName()+"】 address:【"+school.getAddress()+"】");

        Log.e("daggerLog","school add >【"+school+"】");
        Log.e("daggerLog","school2 add >【"+school2+"】");

  }
}

运行后,结果打印如下:


4.png

此时School对象已经注入完毕,可以正常使用了,此处大家可能发现了我们注入了两个School对象,但如果我们想保持School单例的话,该如何操作呢?
当然dagger2不会让我们为这些所担忧,它提供了@Singleton注解用于处理单例的情况;

注意:Singleton是局部单例,和生命周期绑定,注入activity单例范围就是当前activity之内,注入application就是全局单例。

下面我们来验证一下,修改代码,在Module和Component中分别加入@Singleton注解,缺一不可,此时代码如下:

@Module
public class MainModules {

    @Singleton
    @Provides
    public School provideSchool(){
        return new School("南京高级中学","江苏南京");
    }
}

@Singleton
@Component(modules = MainModules.class)
public interface MainComponent {
    void inject(MainActivity activity);
}

运行后,结果打印如下:


5.png

可以看到此时在MainActivity中确实实现了单例,那么我们再次修改下代码,新增SecordActivity,并把School对象注入到SecordActivity中,并在MainActivity中添加跳转按钮,点击后跳转到SecordActivity:

@Singleton
@Component(modules = MainModules.class)
public interface MainComponent {
    void inject(MainActivity activity);
    void inject(SecordActivity activity);
}

public class SecordActivity extends AppCompatActivity {

    @Inject
    School school;

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

        DaggerMainComponent.create().inject(this);

        Log.e("daggerLog","secord school add >【"+school+"】");
    }
}

编译运行后,点击跳转后打印结果如下:

6.png

就像我们前面说的那样,@Singleton是局部单例,此时的两个Activity中的School对象已经是不同的对象了。

我们看下@Singleton的源码:

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

发现里面有个@Scope注解,@Scope其实才是单例的关键,@Scope是用来管理生命周期的,而@Singleton是dagger2通过@Scope提供的一种默认实现。

5.全局单例的实现

下面我们看一下全局单例该如何实现,首先创建MyApplicaiton:

public class MyApplicaiton extends Application {

    private AppComponent appComponent;

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

        appComponent = DaggerAppComponent.builder().build();
    }

    public AppComponent getAppComponent() {
        return appComponent;
    }
}

@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {
    void inject(MainActivity activity);
    void inject(SecordActivity activity);
}

@Module
public class AppModule {

    @Singleton
    @Provides
    public School provideSingleSchool(){
        return new School("全局单例的School","中国");
    }
}

public class MainActivity extends AppCompatActivity {

    @Inject
    School school;

    @Inject
    School school2;

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

        ((MyApplicaiton)getApplication()).getAppComponent().inject(this);


        Log.e("daggerLog","school name:【"+school.getName()+"】 address:【"+school.getAddress()+"】");

        Log.e("daggerLog","school add >【"+school+"】");
        Log.e("daggerLog","school2 add >【"+school2+"】");
    }
}

public class SecordActivity extends AppCompatActivity {

    @Inject
    School school;

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

        ((MyApplicaiton)getApplication()).getAppComponent().inject(this);

        Log.e("daggerLog","secord school add >【"+school+"】");
    }
}

然后运行,跳转到SecordActivity,日志打印如下:


7.png
通过日志可以发现,所有的School对象都是同一对象,实现了全局的单例!

6.如何传递context.

有些时候我们在通过@Provides提供对象的时候需要context参数,形如下面的形式:

public class ShowUtils {
    private Context context;

    @Inject
    public ShowUtils(Context context) {
        this.context = context;
    }
}

这个时候如果我们正常通过@Provides的方式提供的话,dagger是无法确定此处的context是哪种类型的,是Application的?还是Activity的?此时编译就会报错,那么我们该如何指定Context具体是何种类型呢?

此时又出现了一个新的注解@Qualifier,@Qualifier的作用是当dagger无法确定类型的时候,或者说当我们@module里有多个provides提供相同类型的返回值(dagger提供对象是根据返回值匹配的)的时候,对类型进行限定,下面举个例子来体验一下,首先自定义一个限定注解:
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface ForActivityContext {
}

然后我们修改下上面例子中的MainModules:

@Module
public class MainModules {

    private Context context;

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

    @Singleton
    @Provides
    public School provideSchool(){
        return new School("南京高级中学","江苏南京");
    }

    @Singleton
    @Provides
    public ShowUtils provideShowUtils(){
        return new ShowUtils(context);
    }

    //注意这里,此处限定我们使用的context是activity的context
    @ForActivityContext
    @Provides
    public Context provideContext(){
        return context;
    }
}

当然我们还需要在需要此对象的Activity上添加此注解:

@ForActivityContext
public class MainActivity extends AppCompatActivity {

    @Inject
    School school;

    @Inject
    School school2;

    @Inject
    ShowUtils showUtils;

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

        DaggerMainComponent.builder().mainModules(new MainModules(this)).build()
                .inject(this);
        Log.e("daggerLog","school name:【"+school.getName()+"】 address:【"+school.getAddress()+"】");

        Log.e("daggerLog","school add >【"+school+"】");
        Log.e("daggerLog","school2 add >【"+school2+"】");

        Log.e("daggerLog","showUtils add >【"+showUtils+"】");
    }
}

此时运行结果如下,可以看到showUtils对象成功注入到了MainActivity中:


8.png

再看另外一种情况:

当我们需要在一个@module里提供同一对象的不同实例的时候,该如何指定具体需要由哪个@Provides来提供呢?
先看下面一段代码:

    @Singleton
    @Provides
    public School provideSchool(){
        return new School("南京高级中学","江苏南京");
    }

    @Singleton
    @Provides
    public School provideSchoolOther(){
        return new School("国外学校","外国");
    }

其他代码不修改,直接编译会报下面的错误:


9.png

不要慌,@Qualifier来帮你解决这个问题,首先使用@Qualifier自定义一个注解:

@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface SchoolType {
    int type() default 0;
}

然后在@provides中添加此注解:

    @SchoolType
    @Singleton
    @Provides
    public School provideSchool(){
        return new School("南京高级中学","江苏南京");
    }

    @SchoolType(type = 1)
    @Singleton
    @Provides
    public School provideSchoolOther(){
        return new School("国外学校","外国");
    }

在MainActivity中@Inject处指定需要使用那种type的对象来进行注入:

@ForActivityContext
public class MainActivity extends AppCompatActivity {

    @SchoolType
    @Inject
    School school;

    @SchoolType(type = 1)
    @Inject
    School school2;

    @Inject
    Student student;

    @Inject
    ShowUtils showUtils;

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

        DaggerMainComponent.builder().mainModules(new MainModules(this)).build()
                .inject(this);

        Log.e("daggerLog","school name:【"+school.getName()+"】 address:【"+school.getAddress()+"】");

        Log.e("daggerLog","school add >【"+school+"】");
        Log.e("daggerLog","school2 add >【"+school2+"】");
        Log.e("daggerLog","school2 name:【"+school2.getName()+"】 address:【"+school2.getAddress()+"】");

        Log.e("daggerLog","showUtils add >【"+showUtils+"】");
    }
}

运行结果如下:


10.png

可以看到school和school2对象都成功的注入到了MainActivity中,由于我们指定了@SchoolType并且使用了不同的type,所以创建了两个不同的对象。

那么是不是我们需要限定的时候就需要自定义注解呢,当然不是,dagger2为我们提供了一个注解@Named,我们来看下@Named的源码:
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface Named {
    /** The name. */
    String value() default "";
}

是不是瞬间豁然开朗,这不就是我们刚刚定义的@SchoolType一样嘛,只是名字不同罢了,当然把上面的@SchoolType直接替换成@Named也是一样的,不信大家可以试试!

说了这么多,对于dagger基本使用大家是不是有点头绪了,本篇我们主要讲解了基本注解@Inject,@Component,@Module,@Provides,@Qualifier等的使用,当然dagger2还是很多更高阶的用法,比如dependencies依赖,又比如subcomponent注解我们还没有讲到,这些内容准备放在下篇博文中进行讲解,敬请期待!

本文源码地址:
https://github.com/hanxiaofeng/StudyAndroid --- > daggeruse目录

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

推荐阅读更多精彩内容

  • 原文地址Dagger2 入门,以初学者角度 依赖注入 Dagger2是Android中比较热门的依赖注入框架,什么...
    Marlon_IT阅读 1,272评论 1 16
  • 什么的依赖注入 在软件工程中,依赖注入是实现控制反转的方式之一。百度百科中对于控制反转的解释如下:控制反转(Inv...
    小甜李子阅读 1,668评论 5 3
  • 部分内容参考自:[Android]使用Dagger 2依赖注入 - DI介绍(翻译)[Android]使用Dagg...
    AItsuki阅读 47,210评论 66 356
  • Dagger2是个什么东西呢?依赖注入,这是个啥玩意?嗯,在学这个东西的时候我们得了解一些知识点: 知识点呀 依赖...
    SHERLOCKvv阅读 1,189评论 0 6
  • on :在被选元素及子元素上添加一个或多个事件处理程序 on() 方法是 bind()、live() 和 dele...
    wn_Smile阅读 233评论 0 0