Dagger2-如何在运行时有条件地选择模块 [英] Dagger2 - How to conditionally choose modules at runtime

查看:57
本文介绍了Dagger2-如何在运行时有条件地选择模块的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个BIG Android应用,该应用需要运行不同的代码,具体取决于操作系统版本,制造商和许多其他东西.但是,此应用必须是一个APK.它需要在运行时足够聪明才能确定要使用的代码.到目前为止,我们一直在使用Guice,但由于性能问题,我们不得不考虑迁移到Dagger.但是,我无法确定我们是否可以实现相同的用例.

I have a BIG Android app that needs to run different code for depending on the OS version, the manufacturer, and many other things. This app however needs to be a single APK. It needs to be smart enough at runtime to determine which code to use. Until now we have been using Guice but performance issues are causing us to consider migrating to Dagger. However, I've been unable to determine if we can achieve the same use case.

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

The main goal is for us have some code that runs at startup to provide a list of compatible Modules. Then pass that this list to Dagger to wire everything up.

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

Here is some pseudocode of the current implementation in Guice we want to migrate

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())
    }
  }  
}

因此在启动时,应用程序会执行以下操作:

so at startup the app does this:

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

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

where crazyBusinessLogic() reads the annotations of all the modules and determines a single one to use for each feature based on device properties. For example:

  • 操作系统= 5.0的三星设备将具有crazyBusinessLogic()返回列表{new WifiSamsung44Module(),new NfcDefaultModule()}
  • 操作系统= 7.0的三星设备将具有crazyBusinessLogic()返回列表{new WifiSamsung44Module(),new Nfc60Module()}
  • 操作系统= 7.0的Nexus设备将具有crazyBusinessLogic()返回列表{new Wifi44Module(),new Nfc60Module()}
  • 以此类推....

有什么办法可以对付匕首吗? Dagger似乎要求您在Component批注中传递模块列表.

Is there any way to do the same with Dagger? Dagger seems to require you to pass the list of modules in the Component annotation.

我读了一个博客,该博客似乎只适用于一个小型演示,但似乎很笨拙,组件的多余if语句和额外接口可能会使我的代码膨胀.

I read a blog that seems to work on a small demo, but it seems clunky and the extra if statement and extra interfaces for components might cause my code to balloon.

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

有没有办法像使用Guice一样使用从函数返回的模块列表?如果没有,那么最能减少重写注解和crazyBusinessLogic()方法的方法是什么?

Is there any way to just use a list of modules returned from a function like we are doing in Guice? If not, what would be the closest way that would minimize rewriting the annotations and the crazyBusinessLogic() method?

推荐答案

Dagger在编译时生成代码,因此您不会像在Guice中那样具有那么多的模块灵活性. Dagger不再需要Guice能够反思性地发现@Provides方法并运行反思性configure()方法,而是需要知道如何在运行时创建它可能需要的每个实现,并且需要知道在编译时.因此,无法传递任意模块的模块并使Dagger正确连接图形;它破坏了Dagger编写时提供的编译时检查和性能.

Dagger generates code at compile-time, so you are not going to have as much module flexibility as you did in Guice; instead of Guice being able to reflectively discover @Provides methods and run a reflective configure() method, Dagger is going to need to know how to create every implementation it may need at runtime, and it's going to need to know that at compile time. Consequently, there's no way to pass an arbitrary array of Modules and have Dagger correctly wire your graph; it defeats the compile-time checking and performance that Dagger was written to provide.

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

That said, you seem to be okay with a single APK containing all possible implementations, so the only matter is selecting between them at runtime. This is very possible in Dagger, and will probably fall into one of four solutions: David's component-dependencies-based solution, Module subclasses, stateful module instances, or @BindsInstance-based redirection.

您链接的David博客,您可以定义一个带有一组需要传入的绑定的接口,然后通过传递给构建器的该接口的实现来提供这些绑定.尽管该接口的结构经过精心设计,可以将Dagger @Component实现传递到其他Dagger @Component实现中,但是该接口可以通过任何方式实现.

As in David's blog you linked, you can define an interface with a set of bindings that you need to pass in, and then supply those bindings through an implementation of that interface passed into the builder. Though the structure of the interface makes this well-designed to pass Dagger @Component implementations into other Dagger @Component implementations, the interface may be implemented by anything.

但是,我不确定该解决方案是否适合您:此结构也最适合继承独立的实现,而不是在您的各种WifiManager实现都具有图形需要满足的依赖关系的情况下.如果您需要支持插件"体系结构,或者您的Dagger图非常庞大,以至于单个图不应包含应用程序中的所有类,则可能会吸引您使用这种解决方案您可能会发现此解决方案冗长而严格.

However, I'm not sure this solution suits you well: This structure is also best for inheriting freestanding implementations, rather than in your case where your various WifiManager implementations all have dependencies that your graph needs to satisfy. You might be drawn to this type of solution if you need to support a "plugin" architecture, or if your Dagger graph is so huge that a single graph shouldn't contain all of the classes in your app, but unless you have those constraints you may find this solution verbose and restrictive.

Dagger允许使用非final模块,并允许将实例传递到模块中,因此您可以通过将模块的子类传递到组件的生成器中来模拟所采用的方法.由于替代/替代实现的功能通常与测试相关联,因此在-它清楚地描述了此方法的警告,尤其是虚拟方法的调用将比静态@Provides方法慢,并且任何被覆盖的@Provides方法都必须采用 all 参数,而 any 实现用途.

Dagger allows for non-final modules, and allows for the passing of instances into modules, so you can simulate the approach you have by passing subclasses of your modules into the Builder of your Component. Because the ability to substitute/override implementations is frequently associated with testing, this is described on the Dagger 2 Testing page under the heading "Option 1: Override bindings by subclassing modules (don’t do this!)"—it clearly describes the caveats of this approach, notably that the virtual method call will be slower than a static @Provides method, and that any overridden @Provides methods will necessarily need to take all parameters that any implementation uses.

// 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 Component
YourAppComponent component = YourAppComponent.builder()
    .baseWifiModule(new SamsungWifiModule())   // or name it anything
                                               // via @Component.Builder
    .build();

这可行,因为您可以提供一个Module实例并将其视为抽象工厂模式,但是通过不必要地调用new,您并没有充分利用Dagger.此外,需要维护所有可能依赖项的完整列表可能会使此麻烦变得不那么值得了,特别是考虑到您希望所有依赖项都在同一APK中发布. (如果您需要某些类型的插件体系结构,或者希望避免完全基于编译时标志或条件来交付实现,则这可能是一种更轻巧的选择.)

This works, as you can supply a single Module instance and treat it as an abstract factory pattern, but by calling new unnecessarily, you're not using Dagger to its full potential. Furthermore, the need to maintain a full list of all possible dependencies may make this more trouble than it's worth, especially given that you want all dependencies to ship in the same APK. (This might be a lighter-weight alternative if you need certain kinds of plugin architecture, or you want to avoid shipping an implementation entirely based on compile-time flags or conditions.)

提供可能是虚拟模块的能力实际上意味着通过构造函数自变量传递模块实例,然后可以在实现之间进行选择.

The ability to supply a possibly-virtual Module was really meant more for passing module instances with constructor arguments, which you could then use for choosing between implementations.

// 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 Component
YourAppComponent component = YourAppComponent.builder()
    .nfcModule(new NfcModule(true))  // again, customize with @Component.Builder
    .build();

再次,这并未充分发挥Dagger的潜能;您可以通过手动委派所需的正确提供者来实现.

Again, this doesn't use Dagger to its fullest potential; you can do that by manually delegating to the right Provider you want.

// 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提供的任意参数.这导致了第四个解决方案:

Better! Now you don't create any instances unless you need them, and Nfc60Manager and NfcDefaultManager can take arbitrary parameters that Dagger supplies. This leads to the fourth solution:

// 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 Component
YourAppComponent component = YourAppComponent.builder()
    // Use @Component.Builder and @BindsInstance to make this easy
    .yourConfiguration(getConfigFromBusinessLogic())
    .build();

通过这种方式,您可以将业务逻辑封装在自己的配置对象中,让Dagger提供所需的方法,然后返回带有静态@Provides的抽象模块以获得最佳性能.此外,您不需要为API使用Dagger @Module实例,这样可以隐藏实现细节,并在以后需要时更容易从Dagger移开.对于您的情况,我建议使用此解决方案.这将需要一些重组,但我认为您会获得一个更清晰的结构.

This way you can encapsulate your business logic in your own configuration object, let Dagger provide your required methods, and go back to abstract modules with static @Provides for the best performance. Furthermore, you don't need to use Dagger @Module instances for your API, which hides implementation details and makes it easier to move away from Dagger later if your needs change. For your case, I recommend this solution; it'll take some restructuring, but I think you'll wind up with a clearer structure.

打电话给feature.configure(binder())不是习惯用法;请改用 install(feature); .这样,Guice可以更好地描述代码中发生错误的位置,发现模块中的@Provides方法,并在重复安装模块的情况下对模块实例进行重复数据删除.

It's not idiomatic to call feature.configure(binder()); please use install(feature); instead. This allows Guice to better describe where errors occur in your code, discover @Provides methods in your Modules, and to de-duplicate your module instances in case a module is installed more than once.

这篇关于Dagger2-如何在运行时有条件地选择模块的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆