我有一个大型 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);
最佳答案
Dagger 在编译时生成代码,因此您不会像在 Guice 中那样具有模块灵活性;而不是 Guice 能够反射(reflection)地发现 @Provides方法并运行反射 configure()方法,Dagger 需要知道如何创建它在运行时可能需要的每个实现,并且需要在编译时知道这一点。因此,无法传递任意的模块数组并让 Dagger 正确连接您的图形 ;它破坏了编写 Dagger 提供的编译时检查和性能。
也就是说,您似乎可以接受包含所有可能实现的单个 APK,因此唯一的问题是在运行时在它们之间进行选择。这在 Dagger 中是很有可能的,并且可能属于以下四种解决方案之一:David 的基于组件依赖的解决方案、模块子类、有状态模块实例或 @BindsInstance基于重定向。
组件依赖
如 David's blog you linked ,您可以定义一个带有一组需要传入的绑定(bind)的接口(interface),然后通过传递给构建器的该接口(interface)的实现来提供这些绑定(bind)。虽然界面的结构让这个设计得很好,可以通过 Dagger @Component实现到其他 Dagger @Component实现,接口(interface)可以被任何东西实现。
但是,我不确定这个解决方案是否适合您:这种结构也最适合继承独立实现,而不是您的各种 WifiManager 的情况。实现都具有图形需要满足的依赖项。如果您需要支持“插件”架构,或者如果您的 Dagger 图太大以至于单个图不应包含应用程序中的所有类,您可能会被这种类型的解决方案所吸引,但除非您有这些限制您可能会发现此解决方案冗长且具有限制性。
模块子类
Dagger 允许非 final模块,并允许将实例传递到模块中,因此您可以通过将模块的子类传递到组件的构建器来模拟您的方法。因为替换/覆盖实现的能力经常与测试相关联,这在 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 Component
YourAppComponent component = YourAppComponent.builder()
.baseWifiModule(new SamsungWifiModule()) // or name it anything
// via @Component.Builder
.build();
new不必要地,您没有充分发挥 Dagger 的潜力。此外,需要维护所有可能的依赖项的完整列表可能会使这比它的值(value)更麻烦,特别是考虑到您希望所有依赖项都在同一个 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 Component
YourAppComponent component = YourAppComponent.builder()
.nfcModule(new NfcModule(true)) // again, customize with @Component.Builder
.build();
// 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();
}
}
// 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();
@Provides以获得最佳性能。此外,你不需要为你的 API 使用 Dagger @Module 实例,它隐藏了实现细节,如果你的需求发生变化,以后可以更容易地离开 Dagger。对于您的情况,我推荐此解决方案;这需要一些重组,但我认为你会得到一个更清晰的结构。feature.configure(binder()) ;请使用 install(feature); 反而。这允许 Guice 更好地描述代码中出现错误的位置,发现 @Provides模块中的方法,并在模块安装多次的情况下删除模块实例的重复项。
关于android - Dagger2 - 如何在运行时有条件地选择模块,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48874448/
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
出于纯粹的兴趣,我很好奇如何按顺序创建PI,而不是在过程结果之后生成数字,而是让数字在过程本身生成时显示。如果是这种情况,那么数字可以自行产生,我可以对以前看到的数字实现垃圾收集,从而创建一个无限系列。结果只是在Pi系列之后每秒生成一个数字。这是我通过互联网筛选的结果:这是流行的计算机友好算法,类机器算法:defarccot(x,unity)xpow=unity/xn=1sign=1sum=0loopdoterm=xpow/nbreakifterm==0sum+=sign*(xpow/n)xpow/=x*xn+=2sign=-signendsumenddefcalc_pi(digits
作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代
如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby
我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%
在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/
exe应该在我打开页面时运行。异步进程需要运行。有什么方法可以在ruby中使用两个参数异步运行exe吗?我已经尝试过ruby命令-system()、exec()但它正在等待过程完成。我需要用参数启动exe,无需等待进程完成是否有任何rubygems会支持我的问题? 最佳答案 您可以使用Process.spawn和Process.wait2:pid=Process.spawn'your.exe','--option'#Later...pid,status=Process.wait2pid您的程序将作为解释器的子进程执行。除
我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah
我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r