(译) Dagger 与 Android

原文链接

Dagger 2 相较于其它依赖注入框架的优点中,一个主要的优点就是它的严格的代码生成实现(没有反射),这就意味着它能够在 Android 应用中使用。

但是,在 Android 应用中使用 Dagger 的时候,仍有一些事情是需要考虑的。

哲学

虽然 Android 是用 Java 代码写的,但它在样式方面通常有很大不同。通常,存在这样的差异以、为了适应移动平台的独特性能考虑。

但是,通常应用于 Android 代码的许多模式与应用于其他 Java 代码的模式相反。甚至 Effective Java 中的大部分建议都被认为不适合 Android。

为了实现惯用和可移植代码的目标,Dagger 依靠 ProGuard 对已编译的字节码进行后置处理。这允许 Dagger 在服务器和 Android 上产生外观和感觉自然的源代码,同时使用不同的工具链来生成在两个环境中有效执行的字节码。此外,Dagger 有一个明确的目标,即确保它生成的 Java 源代码始终与 ProGuard 优化兼容。

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

注意

Dagger 假定 Android 用户会使用 ProGuard。

推荐的 ProGuard 设置

观看此空间以获取与使用 Dagger 的应用程序相关的 ProGuard 设置。

dagger.android

使用 Dagger 编写 Android 应用程序的一个主要困难是,许多 Android 框架类都由操作系统本身实例化,如 Activity 和 Fragment ,但如果 Dagger 可以创建所有注入的对象,则效果最佳。相反,你必须在生命周期方法中执行成员注入。这意味着许多类最终看起来像:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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!
((SomeApplicationBaseType) getContext().getApplicationContext())
.getApplicationComponent()
.newActivityComponentBuilder()
.activity(this)
.build()
.inject(this);
// ... now you can write the exciting code
}
}

这有一些问题:

  1. 复制粘贴代码使得以后很难重构。随着越来越多的开发人员复制粘贴该块,更少的人会知道它实际上做了什么。
  2. 更重要的是,它需要请求注入类型(FrombulationActivity)来了解其注入器。即使这是通过接口而不是具体类型完成的,它打破了依赖注入的核心原则:类不应该知道它是如何注入的。

dagger.android 中的类提供了一个方法来简化这个模式。

注入 Activity 对象

  1. 在应用程序组件中安装 AndroidInjectionModule,以确保这些基本类型所需的所有绑定都可用。

  2. 首先编写实现 AndroidInjector 的 @Subcomponent,并使用扩展了AndroidInjector.Factory 的 @Subcomponent.Factory:

1
2
3
4
5
@Subcomponent(modules = ...)
public interface YourActivitySubcomponent extends AndroidInjector<YourActivity> {
@Subcomponent.Factory
public interface Factory extends AndroidInjector.Factory<YourActivity> {}
}
  1. 定义子组件后,通过定义绑定子组件工厂的模块并将其添加到注入 Application 的组件,将其添加到组件层次结构中:
1
2
3
4
5
6
7
8
9
10
11
@Module(subcomponents = YourActivitySubcomponent.class)
abstract class YourActivityModule {
@Binds
@IntoMap
@ClassKey(YourActivity.class)
abstract AndroidInjector.Factory<?>
bindYourActivityInjectorFactory(YourActivitySubcomponent.Factory factory);
}

@Component(modules = {..., YourActivityModule.class})
interface YourApplicationComponent {}

专业提示:如果你的子组件及其工厂没有除步骤2中提到的方法或超类型之外的其他方法或超类型,你可以使用 @ContributesAndroidInjector 为你生成它们。而不是第2步和第3步,添加一个返回 Activity 的抽象模块方法,使用 @ContributesAndroidInjector 对其进行注解,并指定要安装到子组件中的模块。如果子组件需要范围,则还要将范围注解应用于方法。

1
2
3
@ActivityScope
@ContributesAndroidInjector(modules = { /* modules to install into the subcomponent */ })
abstract YourActivity contributeYourActivityInjector();
  1. 接下来,使你的应用程序实现 HasActivityInjector 和 @Inject a DispatchingAndroidInjector 以从 activityInjector() 方法返回:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
}
}
  1. 最后,在 Activity.onCreate() 方法中,在调用 super.onCreate();之前调用AndroidInjection.inject(this):
1
2
3
4
5
6
public class YourActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
}
}
  1. 大功告成

它是如何工作的?

AndroidInjection.inject() 从Application 获取 DispatchingAndroidInjector 并将你的 Activity 传递给 inject(Activity)。 DispatchingAndroidInjector 为你的Activity类(即YourActivitySubcomponent.Factory)查找 AndroidInjector.Factory,创建AndroidInjector(即 YourActivitySubcomponent),并将你的Activity 传递给 inject(YourActivity)

注入 Fragment 对象

注入 Fragment 就像注入一个 Activity 一样简单。以相同的方式定义子组件,并将HasActivityInjector 替换为 HasFragmentInjector。

不像在 Activity 类型中那样注入 onCreate(),而是将 Fragment 注入 onAttach()。

与为 Activity 定义的模块不同,你可以选择在何处安装 Fragment 模块。你可以将 Fragment 组件作为另一个 Fragment 组件,Activity 组件或 Application 组件的子组件 - 这一切都取决于 Fragment 所需的其他绑定。在确定组件位置后,使相应的类型实现HasFragmentInjector。例如,如果你的 Fragment 需要来自 YourActivitySubcomponent 的绑定,那么你的代码将如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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.Factory
public interface Factory extends AndroidInjector.Factory<YourFragment> {}
}

@Module(subcomponents = YourFragmentSubcomponent.class)
abstract class YourFragmentModule {
@Binds
@IntoMap
@ClassKey(YourFragment.class)
abstract AndroidInjector.Factory<?>
bindYourFragmentInjectorFactory(YourFragmentSubcomponent.Factory factory);
}

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

基础框架结构

因为DispatchingAndroidInjector在运行时通过类查找适当的AndroidInjector.Factory,所以基类可以实现HasActivityInjector / HasFragmentInjector / etc以及调用AndroidInjection.inject()。所有每个子类需要做的是绑定相应的@Subcomponent。如果您没有复杂的类层次结构,Dagger提供了一些基本类型,例如DaggerActivity和DaggerFragment。 Dagger还为同一目的提供了DaggerApplication - 您需要做的就是扩展它并覆盖applicationInjector()方法以返回应该注入Application的组件。

以下的类型也被包含进去了:

  • DaggerService 和 DaggerIntentService
  • DaggerBroadcastReceiver
  • DaggerContentProvider

注意:只有在AndroidManifest.xml中注册BroadcastReceiver时才应使用DaggerBroadcastReceiver。在你自己的代码中创建BroadcastReceiver时,更喜欢构造函数注入。

支持库

对于 Android 支持库的用户,dagger.android.support 包中存在并行类型。

TODO(ronshapiro):我们应该开始通过 androidx 软件包将其拆分

如何获取它

添加以下的依赖到你的 build.gradle

1
2
3
4
5
dependencies {
compile 'com.google.dagger:dagger-android:2.x'
compile 'com.google.dagger:dagger-android-support:2.x' // if you use the support libraries
annotationProcessor 'com.google.dagger:dagger-android-processor:2.x'
}

什么时候注入

尽可能使用构造函数注入,因为javac将确保在设置之前不引用任何字段,这有助于避免NullPointerExceptions。当需要注射成员时(如上所述),优选尽早注射。出于这个原因,DaggerActivit y在调用 super.onCreate() 之前立即在 onCreate() 中调用AndroidInjection.inject(),而 DaggerFragment在onAttach() 中执行相同操作,这也可以防止重新附加Fragment时出现不一致。

在 Activity 中的 super.onCreate() 之前调用 AndroidInjection.inject() 是至关重要的,因为对super的调用会在配置更改期间附加前一个活动实例中的 Fragments,而后者会注入Fragments。为了使 Fragment 注入成功,必须已经注入了Activity。对于 ErrorProne 的用户,在 super.onCreate() 之后调用 AndroidInjection.inject() 是一个编译器错误。

FAQ

确定 AndroidInjector.Factory 的范围

AndroidInjector.Factory 旨在成为无状态接口,以便实现者不必担心管理与将要注入的对象相关的状态。当 DispatchingAndroidInjector 请求 AndroidInjector.Factory 时,它会通过 Provider 执行此操作,以便它不会显式保留工厂的任何实例。因为某些实现可能会保留正在注入的 Activity / Fragment / etc的实例,所以将范围应用于提供它们的方法是一个编译时错误。如果你肯定你的 AndroidInjector.Factory 没有为注入的对象保留实例,则可以通过将 @SuppressWarnings(“dagger.android.ScopedInjectorFactory”)应用于模块方法来抑制此错误。

非典型前端coder wechat
想要随时Follow我的最新博客,可扫码关注我的公众号