Dagger2.1不是Dagger2

一、前言

在学习android architecture components(简称acc)时下载了google官方demo,demo里有一部分是关于dagger+mvvm(mvp)+acc的,本以为阅读起来没有压力但结果却是一脸懵逼,是的,dagger的写法以及注解完全陌生。难道dagger更新了?本着落后就要挨打的原则去google了一番,发现国内资料通篇都是dagger原本的用法,并没有找到我需要的。最后终于在国外网站找到了一篇dagger2.1新用法的介绍,配合dagger官网终于了解了一二,这也坚定了我从头写一篇Dagger文章的决心。

本文适合人群:1、从未了解过dagger,2、仅简单使用API但不了解为何这样使用,3、可以很好的使用并理解dagger,仅需了解新版本(可以直接跳到第四部分)

二、 Dependency Injection(DI)

翻译成中文也就是依赖注入,我们先用一个很简单的例子来了解一下

    class Clothes {
        final int Defense = 5;
    }

    class Pants {
        final int Defense = 10;
    }

    class hero {

        void printDefense() {
            Clothes clothes = new Clothes();
            Pants pants = new Pants();

            System.out.print("您的角色拥有防御值: "+clothes.Defense + pants.Defense);
        }
    }

这是一段很简单的代码,初始化角色时,拥有衣服裤子两件装备。但仔细查看,在hero类里创建了clothes和pants两个类,造成了耦合,当你修改clothes或pants时很可能影响到hero类。所以我们通常会将上述代码优化如下:

 class hero {
        private Clothes clothes;
        private Pants pants;

        public hero(Clothes clothes, Pants pants) {
            this.clothes = clothes;
            this.pants = pants;
        }

        void printDefense() {
            System.out.print("您的角色拥有防御值: "+clothes.Defense + pants.Defense);
        }
    }

没错放到构造方法里,这就是'依赖注入'了,hero依赖了clothes和pants,同样的,set方法也可以进行依赖注入。大家是不是在不知不觉中也使用过类似的方式呢:)

对比一下两种方式,前者的缺点如下:

1、clothes和pants无法重用,降低了代码的重用性

2、增加了代码的耦合性,一旦clothes和pants更改可能需要修改hero类很多地方

3、很难进行单元测试

好的,接下来我们就开始用依赖注入的方法创建hero

    public static void main(String[] args){
        Clothes clothes = new Clothes();
        Pants pants = new Pants();

        Hero hero = new Hero(clothes, pants);
        hero.printDefense();
    }

这样子看起来没什么大问题,但是当依赖多了,或者依赖也依赖了其他依赖,就会相当臃肿。有点绕吗?没关系我们看代码:


    public static void main(String[] args) {
        Color red = new Color();
        Clothes clothes = new Clothes(red);
        Pants pants = new Pants();
        Shoes shoes = new Shoes();
        Hat hat = new Hat();

        ....

        Hero hero = new Hero(clothes, pants, shoes, hat, ....);
        hero.printDefense();
    }

上面的代码中Clothes又依赖了Color,说不定Color又可能会依赖些什么,加上下面的Pants、Shoes、hat......等等,构造我们的Hero需要太多依赖,这样我们使用hero时就非常不方便,主方法就会越来越难以维护。

三、Dagger基本使用

这样,Dagger就孕育而生了,他是一个DI框架,会让我们的依赖注入工作显得非常轻松。自动生成依赖,而我们只需要添加注解即可。

我们用Dagger的方式来重写上面的代码:


class Clothes {
    final int Defense = 5;

    @Inject
    public Clothes() {
    }
}

class Pants {
    final int Defense = 10;

    @Inject
    public Pants() {
    }
}

class Hero {
    private static final String TAG = "Hero";
    private Clothes clothes;
    private Pants pants;

    @Inject
    public Hero(Clothes clothes, Pants pants) {
        this.clothes = clothes;
        this.pants = pants;
    }

    public void printDefense() {
        Log.e(TAG, "您的角色拥有防御值: " + (clothes.Defense + pants.Defense));
    }
}

是的,就是这么简单,只需要添加@Inject注解,Dagger就能帮我们自动构建Hero对象,当我们使用时就像这样:

Component
interface HeroComponent {
    Hero getHero();
}

    public static void main(String[] args){
//        Clothes clothes = new Clothes();
//        Pants pants = new Pants();

//        Hero hero = new Hero(clothes, pants);
//        hero.printDefense();

        HeroComponent component = DaggerHeroComponent.create();
        Hero hero = component.getHero();
        hero.printDefense();
    }

我们来梳理一下:

1、首先创建一个component接口并添加@component注解,添加获取Hero的方法

2、给Hero构造方法添加@Inject注解,表示需要自动生成该类

3、给依赖类添加@Inject,表示改类也需要自动生成,如此递归添加直到所有依赖都能自动生成

4、构建项目(必须先构建,Dagger会帮你生成一些辅助类),使用名为‘Dagger+YourComponent’的类创建Hero对象

5、愉快的使用吧!

这样一来无论Hero需要多少依赖,我们都可以很简单的生成,不会造成类的臃肿和耦合,使用Hero时不用关心依赖从哪里来,怎么来的,就算Hero或依赖构建方式改变,也不需要修改调用的main函数,很好的解耦。

这时有些童鞋就会问了,如果我需要使用类似Retrofit、OkHttp之类的三方库,而我们却无法去库里添加@Inject注解,该怎么办?没关系,Dagger同样为大家准备了对策:@Module、@Provides

@Module:类似一个仓库,提供Retrofit之流的实例

@Provides:仓库里具体产品的标志,表示该产品对外提供

@Module
public class MyModule {

    @Provides
    public Retrofit ProvideRetrofit() {
        return new Retrofit.Builder()
                .baseUrl("www.google.com")
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .client(setupClient())
                .build();
    }
}

这样就写好了一个提供Retrofit的方法,我们还需要将module提交到component:

Component(modules = MyModule.class)
interface HeroComponent {
    Hero getHero();
    Retrofit getRetrofit();
}

这样,我们就可以愉快的使用Retrofit了,使用方式和Hero一样,我就不累述了。

关于其他API大家可以查阅资料,本文重在思路介绍。

建议初学者在阅读以下内容前先了解更多Dagger2的使用,推荐文章:给初学者的Dagger2

因为以下内容仅适合使用过Dagger2的童鞋了!!!

四、Dagger&Android

打起精神,重点来了!!

在讲解重点之前,我们先来看一段代码:

((MyApplication) getApplication())
        .getAppComponent()
        .myActivity(new MyActivityModule(userId))
        .build()
        .inject(this);

在熟悉使用过Dagger2的coder眼中,这段代码几乎贯穿了整个应用程序,每一个activity都会调用同样的代码,相信大家也想尽办法去优化该段代码,包括将其放入Base中或者做了各种各样的封装,可能都不太尽人意(至少笔者没有找到最优解-_-)。

没错!官方这次升级完美的解决了该问题,以前的实现方式我就不多说了,直接讲述新版本方案。

我先按流程走一遍,让大家熟悉熟悉:

1、老规矩,gradle添加依赖

    implementation 'com.google.dagger:dagger-android:2.15'
    implementation 'com.google.dagger:dagger-android-support:2.15' // if you use the support libraries
    annotationProcessor 'com.google.dagger:dagger-android-processor:2.15'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.15'

笔者写的时候最新版为2.15,大家使用时可以更换为官方最新版。

2、创建AppComponent

@Component(modules = {
        AndroidSupportInjectionModule.class,
        AppModule.class,
        ActivityBuilder.class})
public interface AppComponent  extends AndroidInjector<MyApplication> {

    @Override
    void inject(MyApplication app);

    @Component.Builder
    interface Builder {
        @BindsInstance
        Builder application(Application application);

        AppComponent build();
    }
}

@Component.Builder:自定义component构造器.如果看过Dagger2源码的童鞋应该知道component是用Builder模式创建的,而这里提供了一个自定义构造器。目前先不管,你甚至可以删除这个Builder都没有影响,Dagger会自动生成,这里我只是想展示一下这个注解

继承了AndroidInjector类,重写了他的Inject方法,这个方法必须写,初始化AppComponent用。

@Component(modules = {
        AndroidSupportInjectionModule.class,
        AppModule.class,
        MainActivityModule.class})

Component注解里添加了需要使用到的Module

AndroidSupportInjectionModule.class:这是Dagger2.1初始化必须的module,由Dagger内部完成,用于提供各种AndroidInjector,我们只需要固定加上即可

AppModule.class:用于Application需要初始化提供的全局module,比如刚刚提到的Retrofit、Okhttp等全局对象

ActivityBuilder.class:这就是我们Activity所需的Module了

前两者我们先不关注,重点看看ActivityBuilder,这和我们以前的使用方式就不大相同了,先来看看他的实现。

3、创建ActivityBuilder

@Module
public abstract class ActivityBuilder {

    @Binds
    @IntoMap
    @ActivityKey(MainActivity.class)
    abstract AndroidInjector.Factory<? extends Activity> bindMainActivity(MainActivityComponent.Builder builder);

}

这里是所有Activity(Fragment等)的Component.builder的集合,需要添加@Binds @IntoMap @ActivityKey注解,暂且不管他们的具体作用,只需要了解@Binds就类似于@Provides就可以了。这个类的作用就是让Dagger知道我们所有的使用地。

4、创建MainActivityComponent

@Subcomponent(modules = {MainActivityModule.class})
public interface MainActivityComponent extends AndroidInjector<MainActivity> {
    @Subcomponent.Builder
    public abstract class Builder extends AndroidInjector.Builder<MainActivity> {
    }
}

需要使用@Subcomponent注解,所以不需要再像以前写Inject之类的方法,modules和以前一样

值得一说的就是内部类Builder,是固定写法,和第三步配套使用。

5、创建MainActivityModule

@Module
public class MainActivityModule {
    @Provides
    MainView provideMainView(MainActivity mainActivity){
        return mainActivity;
    }
    @Provides
    MainPresenter provideMainPresenter(MainView mainView, ApiService apiService) {
        return new MainPresenter(mainView, apiService);
    }
}

这个也和以前一样,提供需要的依赖

不要忘记在Appmodule里添加subComponent注释,如果Activity多了这里就会添加非常多的XXComponent

@Module(subcomponents = {MainActivityComponent.class,XX,XX,XX.....})
public abstract class AppModule {

}

6、初始化Application

public class MyApplication extends Application implements HasActivityInjector {
    @Inject
    DispatchingAndroidInjector<Activity> dispatchingAndroidInjector;

    @Override
    public void onCreate() {
        super.onCreate();
        DaggerAppComponent.builder().application(this).build().inject(this);
    }

    @Override
    public AndroidInjector<Activity> activityInjector() {
        return dispatchingAndroidInjector;
    }
}

继承HasActivityInjector,重写activityInjector方法,返回一个DispatchingAndroidInjector

然后在oncreate里初始化

7、MainActivity使用

public class MainActivity extends AppCompatActivity implements MainView {

    @Inject
    MainPresenter presenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        AndroidInjection.inject(this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        presenter.loadMain();
    }

    @Override
    public void onMainLoaded() {
        ((TextView)findViewById(R.id.hw)).setText("hello Dagger2.1");
    }
}

调用AndroidInjection.inject(this)即可,无须再调用一长串Dagger链。

到这里,初步使用就已经结束了,看到这里可能会觉得‘尼玛,好像更麻烦了’,‘绕来绕去头都大了’的感慨,没错,确实是更麻烦了,不过别着急,前面我只是希望大家看看最基础的用法,然后再介绍更简洁的方式,不然大家很可能只会用而不知道其中的来历。

优化一:继承DaggerApplication、DaggerActivity等类

大家应该还记得刚刚提到的Application类的实现,需要:

1、继承HasActivityInjector,2、重写activityInjector方法,3、返回一个DispatchingAndroidInjector

其实不仅是Application,在Activity、Fragment里只要有subcomponent就必须要实现类似的类,举个栗子:

一个Activity里有一个Fragment,fragment需要依赖注入,这时候Activity就必须

1、继承HasFragmentInjector, HasSupportFragmentInjector

2、重写supportFragmentInjector、fragmentInjector方法

3、返回DispatchingAndroidInjector<Fragment>、DispatchingAndroidInjector<android.app.Fragment>

这样的话将会非常麻烦而且重复,但只需要继承了DaggerApplication、DaggerActivity,一切都变得简单了,Dagger2.1都帮你搞定了。这里放出优化后的Application:

public class MyApplication extends DaggerApplication {

    @Override
    protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
        AppComponent appComponent = DaggerAppComponent.builder().application(this).build();
        appComponent.inject(this);
        return appComponent;
    }

}

对比上面的是不是简单了很多,其实Activity会更明显,而且每个Activity节省的代码加起来是不可计数的,就留给大家自己去尝试了。

优化二:使用注解@ContributesAndroidInjector

这是Dagger2.1最强大的注释,会帮助你节省数以万计的代码,可能你看到之前的介绍还并不希望升级/使用Dagger2.1,但是用了他之后你就会爱上他。

还记得我们的ActivityBuilder吗?多了@Binds @IntoMap @ActivityKey注解,可能之前我们根本没有用到过,并用他们关联到Component.Builder,同样是重复的工作。利用@ContributesAndroidInjector可以省略这一切,于是我们的ActivityBuilder就变成了这样:

@Module
public abstract class ActivityBuilder {

    @ContributesAndroidInjector(modules = MainActivityModule.class)
    abstract MainActivity bindMainActivity();

    @ContributesAndroidInjector(modules = {SecondActivityModule.class, SecondFragmentProvider.class})
    abstract SecondActivity bindSecondActivity();
}

而我们的MainActivityComponent类就可以删掉了,是的没错,直接删掉吧!Dagger会帮我们自动生成,这样每个Activity只需要对应一个Module即可,还记得以前每个Activity对应一个Component和一个Module吗?

AppModule也可以不需要列出所有Module了,直接删掉

五、结语

通过优化后,你的代码已经非常简洁了,大家可以尝试用新版的Dagger来写,然后对比以前的方式,你一定会迫不及待使用新版的。

本文完整代码已经提交到GitHub

分为两个分支,master中使用了初始版,complete中使用了简洁的方式,并利用Dagger实现了MVP模式以及绑定Fragment的方式,大家一定要下载阅读,这样更有利于理解。

当然如果觉得有用的话记得点一下star,这是对作者的最大鼓励。

地址:https://github.com/mrqatom/DaggerInjection

下期预告:@Binds详细讲解、Dagger2.1源码讲解

参考文档:

https://medium.com/@iammert/new-android-injector-with-dagger-2-part-1-8baa60152abe

https://medium.com/@harivigneshjayapalan/dagger-2-for-android-beginners-dagger-2-part-i-f2de5564ab25

https://google.github.io/dagger/android

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