Android Dagger 2 与 BaseActivity 减少样板文件

Android Dagger 2 with BaseActivity to reduce boilerplate

当我想将每个 activity 中的一些 Dagger 2 样板代码移动到 BaseActivity 时遇到了一些麻烦。

BaseActivity extends AppCompatActivity

我有多项活动,例如:

ActivityA extends BaseActivity implements InterfaceA;
ActivityB extends BaseActivity implements InterfaceB;
...

在每个 activity 中,我都有这样的方法(其中 X 是 A、B、C、... 对于每个 activity):

public void initActivity() {
   ComponentX compX;
   ...
   compX = appComponent.plus(new ModuleX(this)); // this == InterfaceX
   ...
   compX.inject(this); // this == ActivityX
}

我试图减少这段代码,将其移至父 BaseActivity。但是我在做这件事时遇到了一些问题。我认为也许使用泛型我可以做到,但我不知道具体怎么做。

问题来了:你调用了哪个 inject 方法,Java 可以在编译时确定吗?

"A note about covariance"中所述,Dagger 将为您定义的任何成员注入方法生成代码,但您传入的静态类型。

@Component public interface YourComponent {
  void injectBase(BaseActivity baseActivity);
  void injectA(ActivityA activityA);
  void injectB(ActivityB activityB);
  void injectC(ActivityC activityC);
}

调用 injectA 并传递 ActivityA 实例时,您将获得 ActivityA 中定义的字段的注入,包括 BaseActivity 中的字段。与 ActivityB 和 ActivityC 相同。但是,如果你调用 injectBase,Dagger 只会注入属于 BaseActivity 的字段,即使你传入的对象恰好是 ActivityA、ActivityB 或 ActivityC。 Dagger 在编译时生成代码,所以如果你调用 injectBase,注入只会发生在 BaseActivity 上的字段上——因为那是为 BaseActivity 的成员注入器生成的代码,而这些是 Dagger 唯一知道如何执行的字段为 BaseActivity 参数注入。

自然,因为BaseActivity只知道this是BaseActivity的子类型,所以它只能调用injectBase,不能调用任何具体的子类型。重要的是,即使所有名称 injectBaseinjectA 等等 都相同 (如 inject),这仍然是正确的。 JVM 将选择它可以在编译时确定的最窄的重载,它将是 inject(BaseActivity),它将注入 BaseActivity 的字段而不是子类型。如果您要为它们单独命名,您会看到您调用的是哪一个,以及为什么它不注入子类型字段。

泛型在这里无济于事:您正在寻找组件来生成和调用 ActivityA、ActivityB 和 ActivityC 的成员注入器。泛型将被删除,但此外组件不能采用 BaseActivity 的任意子类:Dagger 不能在编译时为它可能只在运行时遇到的类型生成代码。您确实需要在编译时在 Dagger 中准备这些类型。

解决此问题的一种方法是允许子类型自行注入。子类型知道 this 是 ActivityA(等等),即使代码看起来每个字符都相同,Java 也可以识别正确的类型并正确编译它。

// in BaseActivity
protected abstract void injectDependencies();

// in ActivityA
@Override protected void injectDependencies() { component.injectA(this); }

但是,还有另一个最近发布的选项,使用 dagger.android, which uses Multibindings and (effectively) a Map<Class, MembersInjector> to dynamically inject the specific type you want. This works from a superclass, too, to the point that you can have your Activity extend DaggerActivity and everything will work just the way you'd like. (Consult the dagger.android.support package for your AppCompatActivity equivalent DaggerAppCompatActivity。)