搭建自己的框架之3:项目中引入dagger.android(不讲dagger基础)

Dagger 应该是这么多开源项目最难用的,好在有了Dagger.android(理解起来还是很拗口)
那就直接看源码吧。

Demo On GitHub(MVP-Dagger2-Rxjava2)

春天花会开

后台开发Spring很早很早就有依赖注入,Dragger(2) 出现后大型的Android 项目开发依赖管理也美好了。本文主要是总结使用dagger.android不是dagger,如有错误还请大神指导。

虽然dagger很强大,以前在学习使用Dagger2的时候感觉理解绕,相似的模版代码还很多,哪里需要注入还要写DaggerXXX..inject(this);而且也违背依赖注入的核心原则:一个类不应该知道如何实现依赖注入;它要求注射类型知道其注射器, 即使这是通过接口而不是具体类型完成的。

    //那么多个Activity,到处都要写这种代码你不会觉得烦吗😡
    ((SomeApplicationBaseType) getContext().getApplicationContext())
        .getApplicationComponent()
        .newActivityComponentBuilder()
        .activity(this)
        .build()
        .inject(this);

谷歌大神们又研究出一套专门用于Android的注入方式,拓展Dagger2.android https://google.github.io/dagger//android.html,用了以后你简单的配置后加上下面的代码就可以处处依赖注入了。

        AndroidInjection.inject(this);  //一处声明,处处依赖注入

开始使用

首先在Gradle 中引入依赖,目前dagger-android 还是Beta版本,建议及时更新最新

compile 'com.google.dagger:dagger-android:2.13'
compile 'com.google.dagger:dagger-android-support:2.13'
annotationProcessor 'com.google.dagger:dagger-compiler:2.13'
annotationProcessor 'com.google.dagger:dagger-android-processor:2.13'

为什么不用Dagger2,要等Dagger2.android 出来才用

  • 项目规模小,即使使用Dagger2 也不能显著提高生产力
  • dagger 难以上手,项目组可能难以推广,或者都是Copy
  • 违背依赖注入核心原则,模版代码多

dagger2.Android 的出现很大程度的解决/缓解了以上问题

在项目中快速使用

建议小范围或demo 大胆使用,修改添加一些东西慢慢尝试,注释都在代码里面;基本上的根据已有的规则慢慢熟悉就回了,demo 链接 https://github.com/AnyLifeZLB/MVP-Dagger2-Rxjava2,都在代码里详细的注释了。

此处需要一张图

下面是结合Demo翻译官方的链接 https://google.github.io/dagger//android.html

Dagger 2相比比其他大多数依赖注入框架的主要优势之一就是其严格的代码生成实现(无反射)意味着它可以在Android应用程序中使用。然而,在Android开发中使用Dagger的时候还是有一些注意事项。

哲学问题 Philosophy

虽然编写Android 程序也是使用Java,但在风格方面通常是很不一样的。通常情况下,这种差异是为了适应移动平台独特的 性能考虑。

而且,通常应用于Android的许多模式与Java的模式也不太一样。甚至很多Effective Java这本书上的建议 对于Android开发来说都是不合适的。

为了达到符合习惯和可移植代码的目标,Dagger依靠ProGuard来后处理编译的字节码。这使得Dagger能够在服务器和Android开发生成的源代码看起来和感觉都是非常的相近而又自然,同时使用不同的工具链来产生在两个环境中都能有效执行的字节码。此外,Dagger有一个明确的目标,确保它生成的Java源代码与ProGuard优化保持一致(也就是dagger 生产的代码都支持Produard 的混淆)。

当然,并不是所有的问题都可以用这种方式来解决,但它是提供Android特定兼容性的主要机制。

咱们长话短说

所以在Android开发中使用Dagger最好你也使用ProGuard

推荐的ProGuard设置 Recommended ProGuard Settings

注意在ProGuard设置有对应的考虑你使用了Dagger。
Watch this space for ProGuard settings that are relevant to applications using Dagger.

让Dagger2飞起来的东西- dagger.android

在还没有dagger.android 的时候估计大家都会感觉使用dagger有点繁琐和困难。
在Android开发中使用Dagger的主要困难是之一是许多Android框架类是由操作系统本身进行实例化的,像 Activity和Fragment,但只有Dagger可以创建所有的注入对象系统框架类才能玩好的工作,结果悲剧的是您必须在生命周期方法中执行成员注入。这意味着许多类最终看起来像这个鬼样子:

public class FrombulationActivity extends Activity {
  @Inject Frombulator frombulator;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // DO THIS FIRST. Otherwise frombulator might be null!
   //您必须在生命周期方法中执行成员注入,FrombulationActivity才工作
    ((SomeApplicationBaseType) getContext().getApplicationContext())
        .getApplicationComponent()
        .newActivityComponentBuilder()
        .activity(this)
        .build()
        .inject(this);
    // ... now you can write the exciting code
  }
}

这样就会有些问题
1.即使Dagger使我们的代码耦合性更低,但是如果要面临重构,我们仍然不得不去面对每个Activity中这样数行需要我们「复制」+「粘贴」的代码,这会给我们的重构带来一定的难度(试想一下,如果我们的应用有数十个乃至上百个这样的Activity或者Fragment容器,我们的重构计划,首先就要面对这样数百行的代码)。
并且随着新的开发人员加入(他们也许并不知道这些代码的意义,但是他们会复制粘贴),越来越少的人知道这些代码都干了些什么。
2.更重要的是,它要求注射类型(FrombulationActivity)知道其注射器。 即使这是通过接口而不是具体类完成的,它打破了依赖注入的核心原则:一个类不应该知道如何被依赖注入的。

开心的是Google推出了dagger.android他提供了一种很简洁的方式来解决上述问题.

下面的流程的翻译结合了我在GitHub 的Demo做了一些备注,请知悉,

Injecting Activity objects

  1. 添加 AndroidInjectionModule 到你的application级别的component, 在这里提供全局的基础依赖。在demo中是maincomponent,在这里提供全局的并且是唯一的东西,SharedPrefence,DB,HTTP,etc。
@Singleton
@Component(modules = {
        MainModule.class,              //全局的Module,要确保提供的对象是全局唯一的
        AllActivityModule.class,       //减少模版代码,需要依赖注入的只需要添加两行代码就好了
        AndroidInjectionModule.class,  //在应用程序的MainComponent(applocation 中inject了)中,注入AndroidInjectionModule,
        // 以确保Android的类(Activity、Fragment、Service、BroadcastReceiver及ContentProvider等)可以绑定。
        // 一般把AndroidInjectionModule放在ApplicationComponent中,其他的Component依赖Application即可

        AndroidSupportInjectionModule.class,  //使用的Fragment 是V4 包中的?不然就只需要AndroidInjectionModule
})

//YourApplicationComponent
public interface MainComponent {
    void inject(MyApplication application);
}
  1. 定义好YourActivitySubcomponent接口 并继承AndroidInjector<YourActivity>和@Subcomponent.Builder注解的内部抽象类Builder,该内部类继承extends AndroidInjector.Builder<YourActivity>:

    @Subcomponent(modules = ...)
    public interface YourActivitySubcomponent extends AndroidInjector<YourActivity> {
      @Subcomponent.Builder
      public abstract class Builder extends AndroidInjector.Builder<YourActivity> {}
    }
    

写到这里大家估计有点警觉了,YourActivitySubcomponent?是不是我项目中有很多的Activity都需要写Activity1234Subcomponent?Demo 中就只写了一个BaseActivityComponent,讲完这一小段后面再详细的总结一下。

  1. 定义好了YourActivitySubcomponent后定义一个YourActivityModule,这个YourActivityModule依赖YourActivitySubcomponent及其Builder。当然需要添加到你的ApplicationComponent去(demo 优化了流程,这个ApplicationComponent 就是MainComponent),ApplicationComponent 在我们的Application 中执行了inject。

    @Module(subcomponents = YourActivitySubcomponent.class)
    abstract class YourActivityModule {
      @Binds
      @IntoMap
      @ActivityKey(YourActivity.class)
      abstract AndroidInjector.Factory<? extends Activity>
          bindYourActivityInjectorFactory(YourActivitySubcomponent.Builder builder);
    }
    
    @Component(modules = {..., YourActivityModule.class})
    interface YourApplicationComponent {} //在项目Demo中是MainComponent
    
    

重要提示:在步骤2中,假如你的subcomponent和他的Builder没有其他的方法或者supertypes,你可以简单的使用@ContributesAndroidInjector来简单的帮你生成代码。为了取代步骤2和3,你需要定义一个抽象的module方法返回你的activity并用@ContributesAndroidInjector注解,然后绑定到对应的subcomponent.如果subcomponent需要有作用域scope,你也要使用这个作用域注解module 中的抽象方法。
Pro-tip: If your subcomponent and its builder have no other methods or supertypes than the ones mentioned in step #2, you can use@ContributesAndroidInjector to generate them for you. Instead of steps 2 and 3, add an abstract module method that returns your activity, annotate it with @ContributesAndroidInjector, and specify the modules you want to install into the subcomponent. If the subcomponent needs scopes, apply the scope annotations to the method as well.

```
@ActivityScope
@ContributesAndroidInjector(modules = { /* modules to install into the subcomponent */ })
abstract YourActivity contributeYourActivityInjector();

```

这个重要提示很重要,后面会讲根据提示怎么精简这个流程
这个重要提示很重要,后面会讲根据提示怎么精简这个流程
这个重要提示很重要,后面会讲根据提示怎么精简这个流程

  1. 下一步,让你的application实现 HasActivityInjector 同时 用@Inject 一个DispatchingAndroidInjector<Activity> 对象,该对象是重写的 activityInjector() 方法返回的:

    public class YourApplication extends Application implements HasActivityInjector {
      @Inject DispatchingAndroidInjector<Activity> dispatchingActivityInjector;
    
      @Override
      public void onCreate() {
        super.onCreate();
        DaggerYourApplicationComponent.create()
            .inject(this);
      }
    
      @Override
      public AndroidInjector<Activity> activityInjector() {
        return dispatchingActivityInjector;
      }
    }
    
    
  2. 最后, 在你的Activity的 Activity.onCreate() 方法中, 调用 AndroidInjection.inject(this) ,记得一定要在调用 super.onCreate();之前调用:

    public class YourActivity extends Activity {
      public void onCreate(Bundle savedInstanceState) {
        AndroidInjection.inject(this);
        super.onCreate(savedInstanceState);
      }
    }
    
    
  3. 恭喜你,你已经完成了跑步8公里✅

流程说完了,但是是怎么工作起来的呢?

通过AndroidInjection.inject()方法从Application中获取DispatchingAndroidInjector<Activity>对象,同时将Activity传入inject(Activity)方法中。DispatchingAndroidInjector<Activity>对象通过Activity类查询AndroidInjector.Factory(就是YourActivitySubcomponent.Builder),创建ActivityInjector(就是YourActivitySubcomponent),并将Activity传入它的inject(Activity)方法中。

翻译到这里就差不多了,后面关于Fragment不准备翻译了,GitHub 的Demo里面有怎样注入Activity,Fragment,service 的简单例子。

怎么精简这个流程,应该说怎么少写重复的无意义的模版代码?

YourActivity,

When to inject

Constructor injection is preferred whenever possible because javac will ensure that no field is referenced before it has been set, which helps avoid NullPointerExceptions. When members injection is required (as discussed above), prefer to inject as early as possible. For this reason, DaggerActivity calls AndroidInjection.inject() immediately in onCreate(), before callingsuper.onCreate(), and DaggerFragment does the same in onAttach(), which also prevents inconsistencies if the Fragment is reattached.

It is crucial to call AndroidInjection.inject() before super.onCreate() in an Activity, since the call to super attaches Fragments from the previous activity instance during configuration change, which in turn injects the Fragments. In order for the Fragment injection to succeed, the Activity must already be injected. For users of ErrorProne, it is a compiler error to call AndroidInjection.inject() after super.onCreate().

FAQ

Scoping AndroidInjector.Factory

AndroidInjector.Factory is intended to be a stateless interface so that implementors don’t have to worry about managing state related to the object which will be injected. When DispatchingAndroidInjector requests a AndroidInjector.Factory, it does so through a Provider so that it doesn’t explicitly retain any instances of the factory. Because the AndroidInjector.Builderimplementation that is generated by Dagger does retain an instance of the Activity/Fragment/etc that is being injected, it is a compile-time error to apply a scope to the methods which provide them. If you are positive that your AndroidInjector.Factory does not retain an instance to the injected object, you may suppress this error by applying@SuppressWarnings("dagger.android.ScopedInjectoryFactory") to your module method.

Demo On GitHub(MVP-Dagger2-Rxjava2)

番外篇:Android 中通用的Toolbar和Error,Empty,Loading UI 处理

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

推荐阅读更多精彩内容