Dagger2在Android平台上的新姿势

前言

最近看Google新的框架sample时android-architecture-components发现了dagger2在Android平台上新的写法,很简洁,值得学习。特意看了看dagger2的官方文档Android部分,讲得很详细也很到位。因为是新姿势,中文的学习资料还比较少,所以我决定翻译一下官方文档。以下是主要是对dagger2 Android部分官方文档的翻译,并增加了一些内容帮助理解。文档其它部分的已经有人翻译过了dagger2官方文档中文。如果你还不了解dagger2,或者不太熟悉dagger2中的subcomponent和multibindings的话(以下内容需要熟悉这些内容才能看懂),可以先看一下dagger2官方文档中文。dagger2的学习曲线还是很陡峭的,官方文档永远是最好的学习资料。

Android Architecture Components是2017年Google I/O 大会上新推出的应用框架,主要用于解决UI组件的生命周期和数据持久化问题,帮助我们轻易地处理配置变化(像屏幕旋转)时数据的存储问题。构建感知生命周期的Observer,防止内存泄漏。非常值得学习。
Architecture Components官方文档
中文翻译

我已经放弃使用dagger.android了,具体原因可以查看当定义Dagger2 Scope时,你在定义什么?

开始

相较于其它的依赖注入框架而言,Dagger2其中一个主要的优势是,它不使用反射来生成其实现。这意味着它可以被用在Android应用上(意思就是不影响APP的性能)。然而,把Dagger2应用在Android平台上,还是有一些点需要注意。

dagger.android

在Android平台上使用Dagger的一个主要的不同是,很多类的实例化依赖于操作系统本身,像是Activity和Fragment,但是Dagger最理想的工作方式是它能够构造所有需要注入的类的实例。所以,你必须在它们(Activity、Fragment等)的生命周期中进行成员的注入。很多类看上去跟下面类似:

public class FrombulationActivity extends Activity {
  @Inject Frombulator frombulator;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //先写如下代码, 否则frombulator可能为null!
    ((SomeApplicationBaseType) getContext().getApplicationContext())
        .getApplicationComponent()
        .newActivityComponentBuilder()
        .activity(this)
        .build()
        .inject(this);
    // ...其它代码
  }
}

这么做有以下问题:

  1. 复制粘贴同样的代码使得以后想要重构变得困难。越来越多的这样复制粘贴的代码,开发者反而对这段代码的作用了解的更少。
  2. 更加重要的是,它需要被注入类(FrombulationActivity)知道它的注入类。即使这是通过接口实现的,而不是具体的类。但是,这仍然破坏了依赖注入的的核心原则:一个类不应该对它是如何被注入的有任何的了解。

补充说明:以上这样的代码其实在Android平台上并不常见,更常见的是类似如下的代码:

@Singleton
@Component(modules= AppModule.class)
public interface AppComponent {
    Application getApplication();
    //其它需要暴露出来的类,供dependencies使用
}

@ActivityScope
@Component(dependencies = AppComponent::class, modules= ActivityModule.class)
interface ActivityComponent {
    void inject(YourActivity activity)
}

public class YourActivity extends Activity {
  @Inject Frombulator frombulator;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    
    DaggerActivityComponent.builder()
        .appComponent(YourApp.getAppComponent())
        .activityModule(new ActivityModule(this))
        .build()
        .inject(this);
    // ...其它代码
  }
}

也就是通过component之间依赖(dependencies)的方式来管理不同component之间的关系;而Google文档上的例子是通过子组件(subcomponent)的方式来管理component间的关系,并且如果想使用本文所介绍的新的姿势,就必须使用子组件的方式,抛弃原来依赖的方式。就管理component之间的关系而言,这两种方式都是可以的,我也一直都是使用依赖的方式来构建不同的component,这么做相较于子组件的方式而言,最大的优势就是简单,直接依赖另外一个component,被依赖的component暴露出相应的类即可。而子组件的方式写起来比较麻烦。关键是还得额外学习subcomponent的是怎么回事,光整明白component就够累的了,还要啥subcomponent。 But,解锁了这篇文章介绍的dagger2和Android结合的新姿势,老司机就需要考虑这么一个问题了,需不需要换个姿势呢?!我个人觉得,还是有必要的,原因如下:

  1. 符合依赖注入的的核心原则:一个类不应该对它是如何被注入的有任何的了解。这个核心原则体现在我们的代码上就是,在Activity、Fragment等类需要注入对象时,可以直接使用@Inject注解一个对象即可,不需要生成component(subcomponent)了,使用更加的方便。
  2. 我们都是有追求的老司机,借此学习一下subcomponent的使用有益无害。
  3. 代码量更少。没有最懒的程序猿,只有更懒得程序猿。

Activity的注入

  1. 在你的Application Component中加入AndroidInjectionModule模块,以提供所有基本类型的绑定。
@Singleton
@Component(modules= {
AndroidInjectionModule.class, 
...
})
public interface AppComponent {
    ...
}

AndroidInjectionModule并没有什么特别的,只是一个普通的module,该module提供了5个Map,Map的key是Android四大组件和Fragment的class对象,Map的value是相应的注入器的工厂方法。

@Module
public abstract class AndroidInjectionModule {  
    @Multibinds
    abstract Map<Class<? extends Activity>, AndroidInjector.Factory<? extends Activity>>
      activityInjectorFactories();

    @Multibinds
    abstract Map<Class<? extends Fragment>, AndroidInjector.Factory<? extends Fragment>>
      fragmentInjectorFactories();

    @Multibinds
    abstract Map<Class<? extends Service>, AndroidInjector.Factory<? extends Service>>
      serviceInjectorFactories();

    @Multibinds
    abstract Map<Class<? extends BroadcastReceiver>, AndroidInjector.Factory<? extends BroadcastReceiver>>
      broadcastReceiverInjectorFactories();

    @Multibinds
    abstract Map<Class<? extends ContentProvider>, AndroidInjector.Factory<? extends ContentProvider>>
      contentProviderInjectorFactories(); 
    
    private AndroidInjectionModule() {}
}
  1. 声明你的subcomponent并且实现接口AndroidInjector<YourActivity>,该subcomponent需要有一个被@Subcomponent.Builder注解的并扩展自AndroidInjector.Builder<YourActivity>的构造器:
@Subcomponent(modules = ...)
public interface YourActivitySubcomponent extends AndroidInjector<YourActivity> {
  @Subcomponent.Builder
  public abstract class Builder extends AndroidInjector.Builder<YourActivity> {}
}
  1. 声明过subcomponent之后,把它通过如下方式加入到主component体系中:定义一个提供该subcomponent builder的module,并且把该module加入到你的AppComponent中。
@Module(subcomponents = YourActivitySubcomponent.class)
abstract class YourActivityModule {
  @Binds
  @IntoMap
  @ActivityKey(YourActivity.class)
  abstract AndroidInjector.Factory<? extends Activity>
      bindYourActivityInjectorFactory(YourActivitySubcomponent.Builder builder);
}

@Singleton
@Component(modules = {
AndroidInjectionModule.class,
YourActivityModule.class,...
})
interface AppComponent {}

注意:如果你的subcomponent和它的builder除了第2步中提及的方法或者超类没有其它的内容,你可以用 @ContributesAndroidInjector生成2、3步中的一切。现在不需要步骤2和3,你只需声明一个abstract module,返回你所需的activity(用 @ContributesAndroidInjector注解),可以声明subcomponent需要的其它的module。如果这个subcomponent需要scope注解,也可以声明:

@Module
public abstract class ActivityBulidersModule {
    @ActivityScope
    @ContributesAndroidInjector(modules = {/*subcomponent需要的module*/})
    abstract YourActivity contributeYourActivity();
}

@Singleton
@Component(modules = {
AndroidInjectionModule.class,
ActivityBulidersModule.class,...
})
interface AppComponent {}

@ContributesAndroidInjector注解是dagger-android-2.11中提供的,它会生成如下代码:

@Module(subcomponents = ActivityBulidersModule_YourActivityInjector.YourActivitySubcomponent.class)
public abstract class ActivityBulidersModule_YourActivityInjector {
  private ActivityBulidersModule_YourActivityInjector() {}

    @Binds
    @IntoMap
    @ActivityKey(YourActivity.class)
    abstract AndroidInjector.Factory<? extends Activity> bindAndroidInjectorFactory(
        YourActivitySubcomponent.Builder builder);

    @ActivityScope
    @Subcomponent(modules = {/*subcomponent需要的module*/})
    public interface YourActivitySubcomponent extends AndroidInjector<YourActivity> {
        @Subcomponent.Builder
        abstract class Builder extends AndroidInjector.Builder<YourActivity> {}
    }
}

是不是跟我们自己写的一毛一样,@ContributesAndroidInjector只是帮我们自动生成了一些代码,并没有什么特别的,但前提是第2步没有其它方法或者超类型

  1. 下一步,让你的Application实现HasActivityInjector并且@Inject DispatchingAndroidInjector<Activity>而后从方法activityInjector()(接口HasActivityInjector中的方法)返回:
public class YourApplication extends Application implements HasActivityInjector {
  @Inject 
  DispatchingAndroidInjector<Activity> dispatchingActivityInjector;

  @Override
  public void onCreate() {
    super.onCreate();
    DaggerAppComponent.create()
        .inject(this);
  }

  @Override
  public AndroidInjector<Activity> activityInjector() {
    return dispatchingActivityInjector;
  }
}
  1. 最后,在 Activity.onCreate() 方法中在super.onCreate()之前调用AndroidInjection.inject(this)
public class YourActivity extends Activity {
  public void onCreate(Bundle savedInstanceState) {
    AndroidInjection.inject(this);
    super.onCreate(savedInstanceState);
  }
}
  1. 恭喜你!完成了!

如何工作的?

AndroidInjection.inject() 从Application中获取了一个 DispatchingAndroidInjector<Activity>,并把activity实例传入方法 inject(Activity)中。 DispatchingAndroidInjector 根据activity的class来查找 AndroidInjector.Factory(即 YourActivitySubcomponent.Builder),创建 AndroidInjector (即YourActivitySubcomponent), 然后把你的activity实例传入方法 inject(YourActivity)中。

更多关于实现的原理请查看我的另外一篇文章Dagger2在Android平台上的新魔法

Fragment注入

注入Fragment就跟注入Activity一样。以相同的方式定义subcomponent,把Activity类型替换为Fragment,@ActivityKey替换为@FragmentKey,HasActivityInjector替换为HasFragmentInjector。
和Activity在onCreate()中注入不同,Fragment的注入在方法onAttach()中。

和为Activity添加module不同,在Fragment中你可以选择在哪添加module。你可以把你的Fragment的subcomponent声明为另一个Fragment component的子component,或者是Activity component的子component,或者是Application component的子component——这取决于你想在哪注入你的subcomponent。在决定了Fragment subcomponent是哪个component的子component之后,让相应的类型实现接口HasFragmentInjector。

原文说的很绕,其实上面一段话的核心意思就是,Fragment的subcomponent可以是Fragment、Activity、Application component(或subcomponent)的subcomponent,只要其对应的类型(Fragment、Activity、Application)实现了HasFragmentInjector接口即可。作为对比,Activity则只能是Application component的subcomponent,所以只能是Application实现HasActivityInjector接口。

例如,你的Fragment subcomponent是YourActivitySubcomponent的子component。你的代码类似于此:

public class YourActivity extends Activity
    implements HasFragmentInjector {
  @Inject 
  DispatchingAndroidInjector<Fragment> fragmentInjector;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    AndroidInjection.inject(this);
    super.onCreate(savedInstanceState);
    // ...
  }

  @Override
  public AndroidInjector<Fragment> fragmentInjector() {
    return fragmentInjector;
  }
}

public class YourFragment extends Fragment {
  @Inject SomeDependency someDep;

  @Override
  public void onAttach(Activity activity) {
    AndroidInjection.inject(this);
    super.onAttach(activity);
    // ...
  }
}

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

@Module(subcomponents = YourFragmentSubcomponent.class)
abstract class YourFragmentModule {
  @Binds
  @IntoMap
  @FragmentKey(YourFragment.class)
  abstract AndroidInjector.Factory<? extends Fragment>
      bindYourFragmentInjectorFactory(YourFragmentSubcomponent.Builder builder);
}

@Subcomponent(modules = { YourFragmentModule.class, ... }
public interface YourActivityOrYourApplicationComponent { ... }

同样我们可以简写:

@Module
public abstract class FragmentBuildersModule {
    @ContributesAndroidInjector
    abstract YourFragment contributeYourFragment();
}

Support libraries

对于使用Android support library的用户,有dagger.android.support提供了支持。注意用支持库中的Fragment,应该绑定AndroidInjector.Factory<? extends android.support.v4.app.Fragment>,但是仍应该实现AndroidInjector.Factory<? extends Activity> 而不是 <? extends AppCompatActivity> (或者 FragmentActivity)。dagger.android.support中有AndroidSupportInjectionModule,提供了对android.support.v4.app.Fragment的支持:

@Module(includes = AndroidInjectionModule.class)
public abstract class AndroidSupportInjectionModule {
  @Multibinds
  abstract Map<Class<? extends Fragment>, AndroidInjector.Factory<? extends Fragment>>
      supportFragmentInjectorFactories();

  private AndroidSupportInjectionModule() {}
}

如何获取

在你的build.gradle中增加

dependencies {
  compile 'com.google.dagger:dagger-android:2.x'
  compile 'com.google.dagger:dagger-android-support:2.x' // 如果你使用support libraries
  annotationProcessor 'com.google.dagger:dagger-android-processor:2.x'
}

参考:
Dagger&Android

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

推荐阅读更多精彩内容