【译】Dagger2在Android中的使用

1,164 阅读5分钟

原文

与其他大多数依赖注入框架相比,Dagger2的主要优点之一是其严格生成的实现(无反射)意味着它可以在Android应用程序中使用。但是,在Android应用程序中使用Dagger时仍有一些注意事项。

使用Dagger编写Android应用程序的主要困难之一是很多Android框架类都是由操作系统本身实例化的,例如ActivityFragment,但是如果Dagger可以创建所有注入的对象,则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!
    ((SomeApplicationBaseType) getContext().getApplicationContext())
        .getApplicationComponent()
        .newActivityComponentBuilder()
        .activity(this)
        .build()
        .inject(this);
    // ... now you can write the exciting code
  }
}

注入Activity对象

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

  2. 首先编写一个实现AndroidInjector<YourActivity>@Subcomponent和一个继承AndroidInjector.Builder<YourActivity>@Subcomponent.Builder

@Subcomponent(modules = ...)
public interface YourActivitySubcomponent extends AndroidInjector<YourActivity> {
  @Subcomponent.Builder
  public abstract class Builder extends AndroidInjector.Builder<YourActivity> {}
}
  1. 定义子组件后,通过定义一个绑定子组件层次结构并将其添加到注入应用程序的组件的模块,将其添加到组件层次结构中。
@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 {}

专业提示:如果您的子组件及其构建器没有第2步中提到的其他方法或超类型,您可以使用@ContributesAndroidInjector为您生成它们。添加一个抽象模块方法,该方法返回您的活动,使用@ContributesAndroidInjector对其进行注释,并指定要安装到子组件中的模块,而不是步骤2和3。如果子组件需要作用域,则也可以将范围注释应用于该方法。

@ActivityScope
@ContributesAndroidInjector(modules = { /* modules to install into the subcomponent */ })
abstract YourActivity contributeYourActivityInjector();
  1. 接下来,让您的Application实现HasActivityInjector并且@Inject一个从activityInjector()方法返回的DispatchingAndroidInjector<Activity>
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)
public class YourActivity extends Activity {
  public void onCreate(Bundle savedInstanceState) {
    AndroidInjection.inject(this);
    super.onCreate(savedInstanceState);
  }
}

如何工作

AndroidInjection.inject()Application中获取一个DispatchingAndroidInjector<Activity>并且传递你的activityinject(this)DispatchingAndroidInjector为您的Activity类(它是YourActivitySubcomponent.Builder)查找AndroidInjector.Factory,创建AndroidInjector(它是YourActivitySubcomponent),并将您的Activity传递给inject(YourActivity)

AndroidInjectioninject方法

/**
 * Injects {@code activity} if an associated {@link AndroidInjector} implementation can be found,
 * otherwise throws an {@link IllegalArgumentException}.
 *
 * @throws RuntimeException if the {@link Application} doesn't implement {@link
 *     HasActivityInjector}.
 */
public static void inject(Activity activity) {
  checkNotNull(activity, "activity");
  Application application = activity.getApplication();
  if (!(application instanceof HasActivityInjector)) {
    throw new RuntimeException(
        String.format(
            "%s does not implement %s",
            application.getClass().getCanonicalName(),
            HasActivityInjector.class.getCanonicalName()));
  }
//
  AndroidInjector<Activity> activityInjector =
      ((HasActivityInjector) application).activityInjector();
  checkNotNull(activityInjector, "%s.activityInjector() returned null", application.getClass());

  activityInjector.inject(activity);
}

注入Fragment对象

Injecting a Fragment is just as simple as injecting an Activity. Define your subcomponent in the same way, replacing Activity type parameters with Fragment, @ActivityKey with @FragmentKey, and HasActivityInjector with HasFragmentInjector.

注入一个Fragment像注入一个Activity一样简单。以相同的方式定义你的subcomponent,使用Fragment替换Activity@FragmentKey替换@ActivityKey,HasFragmentInjector替换HasActivityInjector

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

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

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 { ... }

基本框架类型

因为DispatchingAndroidInjector在运行时按类查找适当的AndroidInjector.Factory,所以基类可以实现HasActivityInjectorHasFragmentInjectoretc等等以及调用AndroidInjection.inject()。每个子类都需要做的就是绑定一个对应的@Subcomponent。如果您没有复杂的类层次结构,Dagger会提供一些基本类型,例如DaggerActivityDaggerFragment。Dagger还为同样的目的提供了一个DaggerApplication你需要做的就是扩展它并覆盖applicationInjector()方法来返回应该注入应用程序的组件。

以下类型也包括在内:

  • DaggerService和DaggerIntentService
  • DaggerBroadcastReceiver
  • DaggerContentProvider

注意:DaggerBroadcastReceiver只能在AndroidManifest.xml中注册BroadcastReceiver时使用。在您自己的代码中创建BroadcastReceiver时,请改为使用构造函数注入。

支持库

对于Android支持库的用户,dagger.android.support包中存在并行类型。请注意,尽管支持Fragment的用户必须绑定AndroidInjector.Factory <?extends android.support.v4.app.Fragment>AppCompat用户应该继续实现AndroidInjector.Factory <?extends Activity>而不是<?extends AppCompatActivity>(或FragmentActivity)。

如何获取它

将以下内容添加到您的build.gradle中:

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将确保没有字段在被设置之前被引用,这有助于避免NullPointerException。 当需要成员注射(如上所述)时,倾向于尽早注射。 出于这个原因,DaggerActivity在调用super.onCreate()之前立即在onCreate()中调用AndroidInjection.inject(),并且DaggerFragment在onAttach()中也是这样做的,这也可以防止在重新连接片段时出现不一致。

在Activity中的super.onCreate()之前调用AndroidInjection.inject()是非常重要的,因为超级调用在配置更改期间将前一个活动实例的碎片连接到Fragments,而这又会导致碎片。 为了使片段注入成功,该活动必须已经被注入。 对于ErrorProne的用户,在super.onCreate()之后调用AndroidInjection.inject()是一个编译器错误。

常见问题

AndroidInjector.Factory旨在成为无状态接口,以便实现者不必担心管理与将被注入的对象相关的状态。 当DispatchingAndroidInjector请求一个AndroidInjector.Factory时,它通过一个Provider来这样做,以便它不明确地保留工厂的任何实例。 由于由Dagger生成的AndroidInjector.Builder实现确实保留了正在被注入的Activity / Fragment /等的实例,因此将范围应用于提供它们的方法时发生编译时错误。 如果你肯定你的AndroidInjector.Factory没有为注入对象保留一个实例,你可以通过在模块方法中应用@SuppressWarnings(“dagger.android.ScopedInjectoryFactory”)来消除这个错误。