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
,不能调用任何具体的子类型。重要的是,即使所有名称 injectBase
、injectA
等等 都相同 (如 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。)
当我想将每个 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
,不能调用任何具体的子类型。重要的是,即使所有名称 injectBase
、injectA
等等 都相同 (如 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。)