android–Dagger2– 如何在运行时有条件地选择模块

android–Dagger2– 如何在运行时有条件地选择模块,第1张

概述我有一个大的Android应用程序需要根据 *** 作系统版本,制造商和许多其他东西运行不同的代码.然而,这个应用程序需要是一个APK.它需要在运行时足够聪明才能确定要使用的代码.到目前为止,我们一直在使用Guice,但性能问题导致我们考虑迁移到Dagger.但是,我一直无法确定我们是否可以实现相

我有一个大的Android应用程序需要根据 *** 作系统版本,制造商和许多其他东西运行不同的代码.然而,这个应用程序需要是一个APK.它需要在运行时足够聪明才能确定要使用的代码.到目前为止,我们一直在使用Guice,但性能问题导致我们考虑迁移到Dagger.但是,我一直无法确定我们是否可以实现相同的用例.

主要目标是让我们在启动时运行一些代码,以提供兼容模块的列表.然后将此列表传递给Dagger以连接所有内容.

以下是我们要迁移的Guice中当前实现的一些伪代码

import com.Google.inject.AbstractModule;@Feature("Wifi")public class WifIDefaultModule extends AbstractModule {  @OverrIDe  protected voID configure() {    bind(WifiManager.class).to(WifIDefaultManager.class);    bind(WifiProcessor.class).to(WifIDefaultProcessor.class);  }}@Feature("Wifi")@CompatibleWithMinOS(OS > 4.4)class Wifi44Module extends WifIDefaultModule {  @OverrIDe  protected voID configure() {    bind(WifiManager.class).to(Wifi44Manager.class);    bindProcessor();  }  @OverrIDe  protected voID bindProcessor() {    (WifiProcessor.class).to(Wifi44Processor.class);  }}  @Feature("Wifi")@CompatibleWithMinOS(OS > 4.4)@CompatibleWithManufacturer("samsung")class WifiSamsung44Module extends Wifi44Module {  @OverrIDe  protected voID bindProcessor() {    bind(WifiProcessor.class).to(SamsungWifiProcessor.class);}@Feature("NFC")public class NfcDefaultModule extends AbstractModule {  @OverrIDe  protected voID configure() {    bind(NfcManager.class).to(NfcDefaultManager.class);  }}@Feature("NFC")@CompatibleWithMinOS(OS > 6.0)class Nfc60Module extends NfcDefaultModule {  @OverrIDe  protected voID configure() {    bind(NfcManager.class).to(Nfc60Manager.class);  }}public interface WifiManager {  //bunch of methods to implement}public interface WifiProcessor {  //bunch of methods to implement}public interface NfcManager {  //bunch of methods to implement}public class SuperModule extends AbstractModule {  private final List<Module> chosenModules = new ArrayList<Module>();  public voID addModules(List<Module> features) {    chosenModules.addAll(features);  }  @OverrIDe  protected voID configure() {    for (Module feature: chosenModules) {      feature.configure(binder())    }  }  }

所以在启动时应用程序执行此 *** 作:

SuperModule superModule = new SuperModule();superModule.addModules(crazyBusinessLogic());Injector injector = Guice.createInjector(Stage.PRODUCTION, superModule);

其中,crazyBusinessLogic()读取所有模块的注释,并根据设备属性确定要用于每个功能的单个模块.例如:

> OS = 5.0的三星设备将使用crazyBusinessLogic()返回列表{new WifiSamsung44Module(),new NfcDefaultModule()}
> OS = 7.0的三星设备将使用crazyBusinessLogic()返回列表{new WifiSamsung44Module(),new Nfc60Module()}
> OS = 7.0的Nexus设备将返回List {new Wifi44Module(),new Nfc60Module()}
>等等….

对Dagger有什么办法吗? Dagger似乎要求您传递Component注释中的模块列表.

我读了一个似乎可以在一个小型演示上工作的博客,但它似乎很笨重,而且组件的额外if语句和额外接口可能会导致我的代码膨胀.

https://blog.davidmedenjak.com/android/2017/04/28/dagger-providing-different-implementations.html

有没有办法只使用我们在Guice中执行的函数返回的模块列表?如果不是,那么最小化重写注释和crazyBusinessLogic()方法的最接近的方法是什么?

解决方法:

Dagger在编译时生成代码,因此您不会像在Guice中那样拥有足够的模块灵活性;而不是Guice能够反射性地发现@ProvIDes方法并运行反射的configure()方法,Dagger将需要知道如何在运行时创建它可能需要的每个实现,并且它需要在编译时知道它.因此,无法传递任意数组的模块并让Dagger正确连接图形;它击败了Dagger编写的编译时检查和性能.

也就是说,您似乎可以使用包含所有可能实现的单个APK,因此唯一的问题是在运行时在它们之间进行选择.这在Dagger中是非常可能的,并且可能属于以下四种解决方案之一:DavID的基于组件依赖性的解决方案,Module子类,有状态模块实例或基于@BindsInstance的重定向.

组件依赖性

与在David’s blog you linked中一样,您可以定义一个接口,该接口包含您需要传入的一组绑定,然后通过传递给构建器的该接口的实现提供这些绑定.虽然接口的结构使得这个设计良好,可以将Dagger @Component实现传递给其他Dagger @Component实现,但是接口可以通过任何实现来实现.

但是,我不确定这个解决方案是否适合您:这种结构最适合继承独立实现,而不是在您的各种WifiManager实现都具有图形需要满足的依赖性的情况下.如果您需要支持“插件”架构,或者如果您的Dagger图形太大以至于单个图形不应包含应用程序中的所有类,但除非您有这些约束,否则您可能会被这类解决方案所吸引.您可能会发现此解决方案冗长且具有限制性.

模块子类

Dagger允许非最终模块,并允许将实例传递到模块中,因此您可以通过将模块的子类传递到组件的构建器来模拟您拥有的方法.因为替换/覆盖实现的能力经常与测试相关联,这在Dagger 2 Testing page under the heading “Option 1: Override bindings by subclassing modules (don’t do this!)”中有描述 – 它清楚地描述了这种方法的注意事项,特别是虚拟方法调用将比静态@ProvIDes方法慢,并且任何被覆盖@ProvIDes方法必然需要采用任何实现使用的所有参数.

// Your base Module@Module public class WifiModule {  @ProvIDes WifiManager provIDeWifiManager(Dep1 dep1, Dep2 dep2) {    /* abstract would be better, but abstract methods usually power     * @Binds, @BindsOptionalOf, and other declarative methods, so     * Dagger doesn't allow abstract @ProvIDes methods. */    throw new UnsupportedOperationException();  }}// Your Samsung Wifi module@Module public class SamsungWifiModule {  @OverrIDe WifiManager provIDeWifiManager(Dep1 dep1, Dep2 dep2) {    return new SamsungWifiManager(dep1);  // Dep2 unused  }}// Your Huawei Wifi module@Module public class HuaweiWifiModule {  @OverrIDe WifiManager provIDeWifiManager(Dep1 dep1, Dep2 dep2) {    return new HuaweiWifiManager(dep1, dep2);  }}// To create your ComponentYourAppComponent component = YourAppComponent.builder()    .baseWifiModule(new SamsungWifiModule())   // or name it anything                                               // via @Component.Builder    .build();

这可行,因为您可以提供单个模块实例并将其视为abstract factory pattern,但通过不必要地调用new,您不会充分发挥Dagger的潜力.此外,需要维护所有可能的依赖项的完整列表可能会使它比它的价值更麻烦,特别是考虑到您希望所有依赖项都在相同的APK中发布. (如果您需要某些类型的插件架构,或者您希望避免完全基于编译时标志或条件运送实现,这可能是一个更轻量级的替代方案.)

模块实例

提供可能虚拟模块的能力实际上更适用于使用构造函数参数传递模块实例,然后您可以使用它们在实现之间进行选择.

// Your NFC module@Module public class NfcModule {  private final boolean useNfc60;  public NfcModule(boolean useNfc60) { this.useNfc60 = useNfc60; }  @OverrIDe NfcManager provIDeNfcManager() {    if (useNfc60) {      return new Nfc60Manager();    }    return new NfcDefaultManager();  }}// To create your ComponentYourAppComponent component = YourAppComponent.builder()    .nfcModule(new NfcModule(true))  // again, customize with @Component.Builder    .build();

同样,这并没有充分利用Dagger的潜力;您可以通过手动委派给您想要的正确的提供商来实现.

// Your NFC module@Module public class NfcModule {  private final boolean useNfc60;  public NfcModule(boolean useNfc60) { this.useNfc60 = useNfc60; }  @OverrIDe NfcManager provIDeNfcManager(      ProvIDer<Nfc60Manager> nfc60ProvIDer,      ProvIDer<NfcDefaultManager> nfcDefaultProvIDer) {    if (useNfc60) {      return nfc60ProvIDer.get();    }    return nfcDefaultProvIDer.get();  }}

更好!现在你不创建任何实例,除非你需要它们,Nfc60Manager和NfcDefaultManager可以获取Dagger提供的任意参数.这导致了第四种解决方案:

注入配置

// Your NFC module@Module public abstract class NfcModule {  @ProvIDes static NfcManager provIDeNfcManager(      YourConfiguration yourConfiguration,      ProvIDer<Nfc60Manager> nfc60ProvIDer,      ProvIDer<NfcDefaultManager> nfcDefaultProvIDer) {    if (yourConfiguration.useNfc60()) {      return nfc60ProvIDer.get();    }    return nfcDefaultProvIDer.get();  }}// To create your ComponentYourAppComponent component = YourAppComponent.builder()    // Use @Component.Builder and @BindsInstance to make this easy    .yourConfiguration(getConfigFromBusinessLogic())    .build();

这样,您可以将业务逻辑封装在自己的配置对象中,让Dagger提供所需的方法,然后返回使用静态@ProvIDes抽象模块以获得最佳性能.此外,您不需要为您的API使用Dagger @Module实例,这会隐藏实现细节,并且如果您的需求发生变化,可以更轻松地从Dagger中移开.对于您的情况,我推荐这个解决方案;它需要一些重组,但我认为你最终会有一个更清晰的结构.

关于Guice模块的附注#configure(Binder)

调用feature.configure(binder())并不是惯用的;请改用install(feature);.这使Guice可以更好地描述代码中出现错误的位置,在模块中发现@ProvIDes方法,以及在模块多次安装的情况下重复删除模块实例.

总结

以上是内存溢出为你收集整理的android – Dagger2 – 如何在运行时有条件地选择模块全部内容,希望文章能够帮你解决android – Dagger2 – 如何在运行时有条件地选择模块所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1098425.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-28
下一篇 2022-05-28

发表评论

登录后才能评论

评论列表(0条)

保存