揭开DaggerAndroid的面纱

参考资料:

  1. 官网资料:https://dagger.dev/android
  2. Dagger Android 学习:https://juejin.im/post/5cc72061e51d456e6154b4bc

前言

1. 进入Dagger2的世界 详细讲解了Dagger2 的基础用法,这是DaggerAndroid的基础,不懂的朋友,可以点击去了解一下。

2. Dagger2 和 DaggerAndroid的关系:

DaggerAndroid扩展库 是在Dagger2 的基础上,针对Android基本组件开发定制的一套用于对象创建获取的框架。

3. 为啥写这篇文章?

  1. 有的人已经发现,纯粹使用Dagger2 来进行研发,类似 XXXActivity + XXXComponent + XXXModule,并没有过多的简化代码,反而引入了很多重复的代码。
  2. 另外,还有的人,看了Dagger2 后,再去看一些基于Dagger2 开发的应用,依然看不懂,比如ContributesAndroidInjector,DaggerApplication等,因为这些都是DaggerAndroid开发库的内容。
  3. 网上的DaggerAndroid演示文档,理论知识都很多,但是提供的Demo很少有循序渐进的过程,因此本文档,模拟我之前写过的一个应用,来演示 不使用Dagger2 ,仅使用Dagger2 和使用DaggerAndroid 来进行相同开发的不同点。通过循序渐进的过程,了解DaggerAndroid是如何使用的。

Demo说明:

  1. Demo DaggerAndroidTest工程地址先放到 这里 ,强烈建议配合Demo一起阅读,提取码为:g0pd。如果不能下载,请留言告知。

  2. Dagger2或者DaggerAndroid其本质还是为了创建和提供对象。因此,本Demo将演示全局静态对象,局部静态动态,普通对象等,在不使用Dagger2,仅使用Dagger2和使用DaggerAndroid来开发的对比。

  3. 为了便于布局文件简化,我引入了DataBinding的简单用法,在Demo里面有相关说明,应该至少可以看懂。

  4. 本Demo 借鉴之前开发过的一个类似商店的应用。包含一个登录页面,商店页面,详情页面和我的页面。

  5. 类关系和数据关系说明:

    1. App类,继承Application,提供全局单例数据 :AppData。
    2. LoginActivity,模拟登录页面,可获取AppData,并拥有LoginActivityData的数据。
    3. MainActivity,主页面,管理三个Fragment,底部为三个Button,负责Fragment的切换。可获取AppData,并提供MainActivityData给Fragment使用。因此MainActivityData 是个 局部单例对象。
    4. ShoppingFragment,DetailFragment和MyFragment,可获取AppData(全局单例),MainActivityData(局部单例) 和 FragmentData。
  6. DaggerAndroidTest工程中提供了4个Module,都可以独立运行。所有的Module中,使用的Data数据都是相同的。

    1. dagger_android: 不使用Dagger2,我们常见的创建和获取对象的方式。
    2. dagger_android1 仅使用Dagger2时,如何构建获取对象。
    3. dagger_android2: 不使用ContributesAndroidInjector时,构建获取对象的方式。
    4. dagger_android3: 使用ContributesAndroidInjector时,构建获取对象的方式。
  7. UI 截图说明:


    dagger_android_login.png

    dagger_android_mainact.png

正式开始进入DaggerAndroid 的学习

1. 创建工程,引入依赖,建议这5个依赖的版本都相同。目前能查到的最新半包围2.32.2,如果还有更新的版本,请自行替换。

    //使用Dagger2,具体的版本在http://central.maven.org/maven2上查找
    //目前采用最新的2.23.2版本,如果有最新版本,请自行替换
    version = [
            dagger : "2.23.2"
    ]
    //这几个dagger的版本,最好保持一致。
    dependencies = [
            "dagger" : "com.google.dagger:dagger:${version.dagger}",
            "dagger_compiler" : "com.google.dagger:dagger-compiler:${version.dagger}",
            //后三个是DaggerAndroid相关
            "dagger_android" : "com.google.dagger:dagger-android:${version.dagger}",
            "dagger_android_processor" : "com.google.dagger:dagger-android-processor:${version.dagger}",
            "dagger_android_support" : "com.google.dagger:dagger-android-support:${version.dagger}"
    ]

2. 先演示不使用Dagger2 时,我们一般如何创建和提供数据(不感兴趣的可略过这点):

演示例子在DaggerAndroidTest 工程的dagger_android Module中,可以独立运行

  1. 全局静态对象,在Application创建,并对外提供该对象的public 获取方法。其他调用类,通过 (App)getApplication.getAppData() 的方式进行获取。
//1. 在App类中创建并提供全局使用的对象。因为App类全局唯一,因此AppData可以说是全局单例的。
public class App extends Application {
    private AppData appHelper;
    @Override
    public void onCreate() {
        super.onCreate();
        appHelper = new AppData();
    public AppData getAppHelper(){
        return appHelper;
    }
}
//2. 获取方式:
...
App app = (App) getApplication();
AppData appData = app.getAppHelper(); //获取到AppData数据。
...
  1. LoginActivity类中,除可以获取AppData外,还提供LoginActivityData的创建。直接new一个对象就行。
  2. MainActivity类,除AppData外,再new一个MainActivityData对象。Fragment可以通过getActivity来获取MainActivityData数据,这一点和AppData类似,因此MainActivityData 就是局部单例对象。
  3. XXXFragment,可以获取ActivityData,可以获取AppData,也提供了XXXFragmentData的创建获取。
        //XXXFragment中获取对象的常见方法。
        MainActivity mainActivity = (MainActivity) getActivity();
        //获取MainActivity提供的数据。
        data.actData = mainActivity.getMainData().getInfo();
        //获取App提供的数据
        App app = (App) mainActivity.getApplication();
        data.appName = app.getAppHelper().getAppName();
        data.appVersion = app.getAppHelper().getVersion();
        data.userName = app.getAppHelper().getUserName();
        data.userPassword = app.getAppHelper().getPassword();
        //获取自身数据
        data.info = new XXXInfo();
  1. 这个没啥说的,直接看DaggerAndroid工程中 dagger_android 就行了。

2. 仅使用Dagger2 时,如何创建对象获取对象。

演示例子在DaggerAndroidTest 工程的dagger_android1 Module中,可以独立运行

Dagger2的具体用法,在上一篇中已经说过了,这次只做简单说明

  1. App中的注入:
    @Override
    public void onCreate() {
        super.onCreate();
        appComponent = DaggerAppComponent.builder().application(this).build();
        appComponent.inject(this);
    }
    //还要对外提供该Component接口,为简化,提供静态接口。
    public static AppComponent getAppComponent(){
        return appComponent;
    }
  1. Activity中的大致注入方式:
    ...
    DaggerLoginActivityComponent.builder().appComponent(App.getAppComponent()).build().inject(this);
    ...
  1. 还有很多的XXXComponent,XXXModule 直接在Demo里面有。
  2. XXXFragment的注入,没有实现,层级不好搭建,只为演示Dagger2 如何创建注入对象,目的已经达到。

3. 使用DaggerAndroid扩展库,但不使用ContributesAndroidInjector,来演示对象的创建和获取。

演示例子在DaggerAndroidTest 工程的dagger_android2 Module中,可以独立运行

  1. 该演示Demo会采用XXXModule + XXXSubcomponent的方式设计。
  2. 先看AppData的注入获取方式。
//1. AppComponent 可以看出来,不使用dependence,直接全部依赖XXXModule。
@Singleton
@Component(modules = {AndroidInjectionModule.class,
                AndroidSupportInjectionModule.class,
                AppModule.class,
                LoginActivityModule.class,
                MainActivityModule.class})
public interface AppComponent { ... }
//2. App类中的注入方式。这是一组固定搭配。使用DaggerAndroid时,Application需要继承HasActivityInjector或者HasSupportFragmentInjector。
//我没有仔细研究,应该是为了分发在Activity或者Framgent中的注入用的。
//因为之后的Activity中,就可以直接是用AndroidInjection.inject(this);来完成注入代码。
public class App extends Application implements HasActivityInjector, HasSupportFragmentInjector {
    @Inject
    DispatchingAndroidInjector<Activity> dispatchingAndroidInjector;
    @Inject
    DispatchingAndroidInjector<Fragment> dispatchingAndroidSupportInjector;
    @Override
    public void onCreate() {
        super.onCreate();
        appComponent = DaggerAppComponent.builder().application(this).build();
        appComponent.inject(this);
    }
        @Override
    public AndroidInjector<Activity> activityInjector() {
        return dispatchingAndroidInjector;
    }

    @Override
    public AndroidInjector<Fragment> supportFragmentInjector() {
        return dispatchingAndroidSupportInjector;
    }
  1. AndroidInjectionModule 和AndroidSupportInjectionModule 是DaggerAndroid提供的Module,简单理解为内部会使用Map的形式来提供对象。AndroidSupportInjectionModule是使用v4包的FragmentActivity时,可以添加。
  2. 在来看LoginActivity相关的代码
//1. LoginActivityModule的实现。
@Module(subcomponents = LoginActivityComponent.class)
public abstract class LoginActivityModule {
    //@Binds用来提供接口,这在Dagger2 中已经说过了。
    //@IntoMap + @ClassKey 这是一组固定搭配。就是说如下方式提供的构造器,会映射到某个Map中。用ClassKey来区分。
    //回想上AndroidInjectionModule 里面有Map,是不是有点联系了,就是添加到了这个Map里面。
    @Binds
    @IntoMap
    @ClassKey(LoginActivity.class)
    abstract AndroidInjector.Factory<?> bindLoginActivityInjectorFactory(LoginActivityComponent.Builder builder);
    
    //如果XXXModule已经有了@Binds,再使用@Provides时,需要使用static。
    @Provides
    static LoginActivityData provideData(){
        return new LoginActivityData();
    }
//2. LoginActivityComponent 修改如下,也算是固定搭配了。AndroidInjector.Builder为已经过时的方法。
@Subcomponent(modules = {AndroidInjectionModule.class})
public interface LoginActivityComponent extends AndroidInjector<LoginActivity> {
    @Subcomponent.Builder
    abstract class Builder extends AndroidInjector.Builder<LoginActivity>{
    }
}
//3. LoginActivity的onCreate接口中,增加入住代码,之后就可以使用AppData和LoginData了。
    ...
    AndroidInjection.inject(this);
    ...
  1. 参考LoginActivity相关的代码,依葫芦画瓢,可以和容易的写出MainActivity相关的注入代码。
  2. XXXFragment的注入我们没有继续实现,因为有AppData的全局单例数据和LoginActivity这样的普通数据,已经可以说明一些注入步骤了。

4. 使用DaggerAndroid扩展库,并使用ContributesAndroidInjector来演示对象的创建和注入(这是重点内容哦~)

演示例子在DaggerAndroidTest 工程的dagger_android3 Module中,可以独立运行

  1. 在dagger_android2 中,依然存在XXXActivity + XXXModule + XXXSubcomponent的组合,重复代码还是挺多的,而且固定代码也很多,怎么办?使用ContributesAndroidInjector就可以再一次简化代码。
  2. 先看Application相关代码,对比上面的dagger_android2,少了一些代码了。
//1. AppModule类不做修改,AppComponent的实现。和上面的相比,需要继承AndroidInjector<App>。
@AppScope
@Component(modules = {AndroidInjectionModule.class, AndroidSupportInjectionModule.class,
        AppModule.class,
        BuildsActivityModule.class})
public interface AppComponent extends AndroidInjector<App> {
    void inject(App app);

    //下面这一堆是个固定搭配,目的给AppModule中的@Provide接口,提供外部参数。可以自行修改。
    @Component.Builder
    interface Builder {
        AppComponent build();
        @BindsInstance Builder application(Application app);
    }
}
//2. App类,继承DaggerApplication,接口中实现注入代码即可。注意继承dagger.android.support.DaggerApplication.
public class App extends DaggerApplication {
    @Override
    protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
        return DaggerAppComponent.builder().application(this).build(); //在该位置注入。
    }
}
  1. 多了一个BuildsActivityModule,开始使用ContributesAndroidInjector自动构造对象,本例中构造了LoginActivity和MainActivity类。同时这两个类,都可以有自己XXXModule,来提供自身使用的数据。
@Module
public abstract class BuildsActivityModule {
    /**
     * TODO 知识点:添加了 @ContributesAndroidInjector 后,就不需要 @IntoMap 和 @ClassKey 了。因为会自动添加。
     * 如下接口,会自动生成 BuildsActivityModule_ContributeLoginActivity 类
     * 该类中会自动使用 @IntoMap 和 @ClassKey。
     * @return
     */

    @ContributesAndroidInjector(modules = LoginActivityModule.class)
    abstract LoginActivity contributeLoginActivity();

    /**
     * TODO 知识点:
     * 如果MainActivityModule需要使用@Scope,同样需要再如下位置添加相同的作用域。
     * 否则自动生成的ActivitySubComponent 会缺少@Scope。
     * @return
     */
    @ActivityScope
    @ContributesAndroidInjector(modules = {MainActivityModule.class,BuildsFragmentModule.class})
    abstract MainActivity contributeMainActivity();
}
  1. contributeMainActivity() 还依赖了个BuildsFragmentModule,如下。依然使用ContributesAndroidInjector来自动构造三个Fragment,且各自Fragment还可以拥有自己独立使用数据的Module。
@Module
public abstract class BuildsFragmentModule {

    @FragmentScope
    @ContributesAndroidInjector(modules = ShoppingFragmentModule.class)
    abstract ShoppingFragment contributeShoppingFragment();

    @ContributesAndroidInjector(modules = DetailFragmentModule.class)
    abstract DetailFragment contributeDetailFragment();

    @ContributesAndroidInjector(modules = MyFragmentModule.class)
    abstract MyFragment contributeMyFragment();
}
  1. @Module中的每一个Activity使用@ContributesAndroidInjector标注后,会自动生成的@Module类和@Subcomponent类。比如LoginActivity,会自动生成BuildsActivityModule_ContributeLoginActivity类和BuildsActivityModule_ContributeLoginActivity.LoginActivitySubcomponent类。MainActivity也一样,甚至Fragment也是一样的。
  2. @ContributesAndroidInjector自动生成的Module和Subcomponent类,和上一节中的Demo中dagger_android2代码类似。这也是上一节中演示不使用ContributesAndroidInjector的目的。
//这是使用ContributesAndroidInjector后,自动生成的@Module类和@Subcomponent类。会自动使用@Binds,@IntoMap,@ClassKey。
@Module(
  subcomponents = BuildsActivityModule_ContributeLoginActivity.LoginActivitySubcomponent.class
)
public abstract class BuildsActivityModule_ContributeLoginActivity {
  private BuildsActivityModule_ContributeLoginActivity() {}
  @Binds
  @IntoMap
  @ClassKey(LoginActivity.class)
  abstract AndroidInjector.Factory<?> bindAndroidInjectorFactory(
      LoginActivitySubcomponent.Factory builder);
//注意:@Subcomponent以内部类的方式实现。
  @Subcomponent(modules = LoginActivityModule.class)
  public interface LoginActivitySubcomponent extends AndroidInjector<LoginActivity> {
    @Subcomponent.Factory
    interface Factory extends AndroidInjector.Factory<LoginActivity> {}
  }
}
  1. 现在来看LoginActivity,直接继承DaggerActivity,啥都不用写。这其实和上一节中的LoginActivity类似,只是DaggerActivity进行了相关的封装。DaggerActivity里面已经使用了inject接口。
//LoginActivity只要继承DaggerActivity就可以了,啥都不用写,可以直接@Inject来获取对象。
public class LoginActivity extends DaggerActivity {...}
  1. 再看MainActivity,由于没有DaggerFragmentActivity,因此可以仿照DaggerActivity自己写一个,也很简单,或者用原始的方法,继承FragmentActivity,实现HasAndroidSupportInjector接口就行。
public class MainActivity extends FragmentActivity implements HasSupportFragmentInjector, HasActivityInjector {
 @Inject
    DispatchingAndroidInjector<Activity> activityInjector;
    @Inject
    DispatchingAndroidInjector<Fragment> fragmentInjector;
    @Override
    public AndroidInjector<Fragment> supportFragmentInjector() {
        return fragmentInjector;
    }

    @Override
    public AndroidInjector<Activity> activityInjector() {
        return activityInjector;
    }
        @Override
    protected void onCreate(Bundle savedInstanceState) {
        AndroidInjection.inject(this); //需要手动注入代码,一定要写在super方法前面。
        super.onCreate(savedInstanceState);
    }
}
  1. XXXFragment也只需要继承DaggerFragment,就可以实现对象的自动注入代码了。唯一的不同点就是,在Fragment中,注入代码的位置在onAttach接口中。
  2. 此时层级比较清晰,另外,任何一个项目,一定要条例清晰,这样设计才不会有大的偏差。


    AppDemo的示意图.png
  1. DaggerAndroidTest这个Demo的扩展:这个Demo是目前常见的应用研发的一种简单形式。实际上AppData中,可以提供一些网络管理器的获取和DB管理器等的创建和获取,这样,所有的Activity,Fragment等都可以使用。同样的MainActivityData和XXXFragmentData都可能会提供一些复杂的逻辑类对象。

最后的总结:

  1. 经过上面的学习,DaggerAndroid扩展库应该可以正常使用了,如果还不能正常使用,就得多写,多调试,因为DaggerAndroid如果不熟悉的话,很容易就会写错。
    一般这样建议是:和Dagger相关的,一次性不要写太多代码,确保写过的代码可以编译通过。是在找不出来出错点,可以通过慢慢恢复代码的方式来定位问题。我是用了git仓库管理,来比对自己的改动点。
  2. 在正式的项目开发中,Dagger2 一般不会独立使用。还会配合 MVVP模式中比如LiveData,Room,还有一些比较好的组件,比如RxAndroid等等,一起进行项目开发。
  3. 此时一定要明确每一个三方库的作用是什么?比如DaggerAndroid的就是 为项目开发中创建和提供对象用的。 MVVP 只是个设计模式,是一个思想。RxAndroid是一个异步任务处理库等等。这样当使用多个三方库时,就不会弄混。
  4. 有问题,可以留言,我看到了就会回复的。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,384评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,845评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,148评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,640评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,731评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,712评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,703评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,473评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,915评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,227评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,384评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,063评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,706评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,302评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,531评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,321评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,248评论 2 352