【问题标题】:Dagger 2 on Android @Singleton annotated class not being injectedAndroid @Singleton 注释类上的 Dagger 2 未被注入
【发布时间】:2015-10-03 17:59:22
【问题描述】:

我目前正在尝试将 Dagger 2 集成到 Android 应用程序中。我的项目设置如下:

  • 图书馆
  • 应用程序(取决于库)

在我的库项目中,我定义了一个类,稍后我将注入到库中其他需要它的类(活动和常规类)以及应用程序项目中。

@Singleton
public class MyManager{
  @Inject
  public MyManager(){
    //Do some initializing
  }
}

现在 - 例如在我的片段或活动或常规课程中,我将注入上述单例,如下所示:

public class SomeClass{

  @Inject
  MyManager myManager;
}

我是这么想的,因为实际上 myManager 总是为空。显然它的构造函数也从未被调用过,所以我想我一定是在配置方面遗漏了一些东西?或者也许我误解了文档,它根本不打算以这种方式工作? MyManager 类的目的是成为一个应用程序范围内可访问的组件累积实体——这就是我选择@Singleton 的原因。

更新

为了避免混淆:我在评论的某处提到了我的组件——这是指“基于组件的设计”意义上的组件,与匕首无关。我拥有的基于 dagger 的代码都在上面列出 - 在我的代码中没有其他与 dagger 相关的内容。

当我开始添加 @Component 时,我遇到了一些编译器问题,因为我的 dagger2 没有正确设置 - 查看这个关于如何正确设置 dagger2 的非常有用的线程:https://stackoverflow.com/a/29943394/1041533

更新 2

这是我根据 G. Lombard 的建议更新的代码 - 我将代码更改如下 - 原来的 Singleton 在库项目中:

@Singleton
public class MyManager{
  @Inject
  public MyManager(){
    //Do some initializing
  }
}

库项目中还有引导类:

@Singleton
@Component
public interface Bootstrap {
    void initialize(Activity activity);
}

然后我在我的活动中使用上面的 Bootstrap 类(在我的具体应用程序中,NOT 在库项目中!但是我在库中也有类/活动,它们将访问 Bootstrap 以注入我的经理):

public class MyActivity extends Activity{

    @Inject
    MyManager manager;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //DONT DO THIS !!! AS EXPLAINED BY EpicPandaForce
        DaggerBootstrap.create().initialize(this);
    }
}

但即使在这行之后:

        DaggerBootstrap.create().initialize(this);

管理器实例仍为空,即未注入。

我刚刚发现了这个:https://stackoverflow.com/a/29326023/1041533

如果我没有误读,这意味着我需要指定 Bootstrap 类中的每个类,这些类将使用 @Inject 来注入东西。可悲的是 - 这不是一个选择,因为我有 40 多个课程和活动我必须这样做。

这意味着我的 Bootstrap 界面显然必须看起来像这样:

@Singleton
@Component
public interface Bootstrap {
    void initialize(ActivityA activity);
    void initialize(ActivityB activity);
    void initialize(ActivityC activity);
    void initialize(ActivityD activity);
    void initialize(ActivityE activity);
    void initialize(ActivityF activity);
    //and so on and so forth...
}

如果上述情况属实,那对于我的用例来说就不值得了。另外:如果我忘记在这里指定我的 40 多个类之一,似乎没有编译时检查?它只是不起作用 - 即在运行时使应用程序崩溃。

【问题讨论】:

  • 您对“没有编译时检查”的假设是不正确的,除非您为同一个类同时指定超类和具体类(即编译检查开始失败并且您需要警惕空值)。这不是匕首1。
  • 啊。那么你需要手动调用现场注入。编译时检查是,如果您尝试使用组件注入一个类,那么它需要定义一个 void inject(YourClass yourClass); 方法,否则它将无法工作。回到 Dagger1 的时代,你必须在 injects={YourClass.class} 类型的数组中提供它,如果你搞砸了,它实际上确实会引发运行时异常!这很糟糕。我们在 ApplicationComponent 中定义了大约 53 个用于字段注入的类,并且提供的类(非生命周期)可以在其构造函数中调用注入。瞧!
  • 好吧,他们需要在创建时手动调用组件的注入方法。如果他们真的记得调用inject,那么它会抱怨需要如何将它添加到组件中。我想说“他们忘记注入”就像说“我用注释标记了视图字段,但我的视图为空”,因为他们忘记在 setContentView(R.layout.something); 之后调用 ButterKnife.bind(this);... 他们会发现这是有点需要让它工作。我的同事对匕首(和依赖注入)完全陌生,他很快就学会了。
  • 你的Bootstrap 类是一个真正的匕首组件,它被这样注释。方法的名称无关紧要,可以调用injectinitialize 或任何方法,只要它的返回类型是void 并且参数是您要注入的任何参数。
  • @EpicPandaForce 非常感谢您清理这个问题!您的 cmets 对了解 dagger2 的工作原理非常有帮助!

标签: java android dagger dagger-2


【解决方案1】:

你犯了一个错误,你正在使用

DaggerBootstrap.create().initialize(this);

在您的 Activity 中,因为范围不会在多个组件实例之间共享。我推荐的是使用自定义应用程序类

public class CustomApplication extends Application {
    @Override
    public void onCreate() {
         super.onCreate();
         Bootstrap.INSTANCE.setup();
    }
}

@Component
@Singleton
public interface _Bootstrap {
    void initialize(ActivityA activityA);
    //void initiali...
}

public enum Bootstrap {
    INSTANCE;

    private _Bootstrap bootstrap;

    void setup() {
        bootstrap = Dagger_Bootstrap.create();
    }

    public _Bootstrap getBootstrap() {
        return bootstrap;
    }
}

那么你可以称它为

Bootstrap.INSTANCE.getBootstrap().initialize(this);

这样,您可以在类之间共享组件。我个人将Bootstrap命名为injector,将_Bootstrap命名为ApplicationComponent,所以看起来是这样的:

Injector.INSTANCE.getApplicationComponent().inject(this);

但这只是我的典型设置。名字并不重要。

编辑:对于你的最后一个问题,你可以通过子范围和组件依赖来解决这个问题。

您的库项目应该只能看到库类,对吗?在这种情况下,你所做的就是

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface LibraryScope {
}

@Component(modules={LibraryModule.class})
@LibraryScope
public interface LibraryComponent {
    LibraryClass libraryClass(); //provision method for `MyManager`
}

@Module
public class LibraryModule {
    @LibraryScope
    @Provides
    public LibraryClass libraryClass() { //in your example, LibraryClass is `MyManager`
        return new LibraryClass(); //this is instantiation of `MyManager`
    }
}

public enum LibraryBootstrap {
    INSTANCE;

    private LibraryComponent libraryComponent;

    static {
        INSTANCE.libraryComponent = DaggerLibraryComponent.create();
    }

    public LibraryComponent getLibraryComponent() {
        return libraryComponent;
    }
}

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ApplicationScope {
}

@Component(dependencies={LibraryComponent.class}, modules={AdditionalAppModule.class})
@ApplicationScope
public interface ApplicationComponent extends LibraryComponent {
    AdditionalAppClass additionalAppClass();

    void inject(InjectableAppClass1 injectableAppClass1);
    void inject(InjectableAppClass2 injectableAppClass2);
    void inject(InjectableAppClass3 injectableAppClass3);
}

@Module
public class AdditionalAppModule {
    @ApplicationScope
    @Provides
    public AdditionalAppClass additionalAppClass() { //something your app shares as a dependency, and not the library
        return new AdditionalAppClass();
    }
}

public enum ApplicationBootstrap {
    INSTANCE;

    private ApplicationComponent applicationComponent;

    void setup() {
        this.applicationComponent = DaggerApplicationComponent.builder()
                                        .libraryComponent(LibraryBootstrap.INSTANCE.getLibraryComponent())
                                        .build();
    }

    public ApplicationComponent getApplicationComponent() {
        return applicationComponent;
    }
}

然后

@Inject
LibraryClass libraryClass; //MyManager myManager;

...
    ApplicationBootstrap.INSTANCE.getApplicationComponent().inject(this);

【讨论】:

  • 非常感谢您的最后一部分!我将在接下来的 2-3 天内尝试一下,如果它有效,我会将您的答案标记为已接受的答案,因为它将为我的具体问题提供一个完整的、有效的解决方案,这将有助于面临类似问题的人.再次感谢您的帮助!
  • LibraryClass 将是您的MyManager,是的。我在编辑中添加了三个 cmets 以使其更清晰。
  • 您需要将构造函数注入替换为模块提供程序,因为您正在使用组件依赖项进行子范围化,并且需要为您的组件提供模块和配置方法。一个模块所做的只是为你想要注入的东西创建一个新实例。
  • 非常感谢您的澄清 :) !明天我会继续,并会在这里报告。
  • 不,这是您想在应用程序中使用的东西,但不是库的一部分。
【解决方案2】:

很难说你的问题是什么,因为你没有显示你的 Component 是什么样子以及你是否有多个组件等。

假设这个逻辑结构:

/app
   MainComponent
   SomeClass    // where MyManager is to be injected
   MainActivity // where SomeClass is to be injected
/library
   LibraryComponent
   MyManager    // Singleton

然后您列出的类将使用以下配置正确注入:

@Singleton
@Component
public interface LibraryComponent {
    MyManager getMyManager();
}

以及将依赖项注入活动的应用级组件:

@ActivityScope
@Component(dependencies = LibraryComponent.class)
public interface MainComponent {
    void inject(MainActivity mainActivity);
}

注意MainComponent 依赖于LibraryComponent,但是因为后者有单例范围,所以你也需要为另一个定义一个范围,我在这里使用了“活动范围”。 (或者,您也可以只将 MainComponent 设为单例,然后完全摆脱 LibraryComponent 如果这适合您的需要。)

最后像这样全部注入到活动中:

@Inject
SomeClass someClass;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    DaggerMainComponent.builder()
            .libraryComponent(DaggerLibraryComponent.create())
            .build()
            .inject(this);

    someClass.doSomething();
}

我已经放了一个工作样本here on GitHub

更新 1:

如果我正确理解您的设置,那么到目前为止,您只在列出的两个类(MyManagerSomeClass)上使用了 @Singleton@Inject 注释,并且没有其他与 Dagger 相关的代码在你的项目中。

在这种情况下,MyManager 没有被注入的原因是 Dagger 不知道如何提供/实例化依赖项。这就是我上面提到的“组件”的来源。如果没有任何 Dagger 2 组件(使用 @Component 注释的接口或抽象类),您的依赖项将不会自动注入。

我不知道你是否有依赖注入概念的经验,但假设你没有,我将逐步介绍你需要了解的最低限度的基础知识,以便将你的MyManager 注入SomeClass

首先:在使用 DI 时,需要了解“newables”和“injectables”的区别。 Misko Hevery 的This blogpost 解释了细节。

这意味着,您不能 new 升级您的 SomeClass。这行不通:

mSomeClass = new SomeClass();

因为如果你这样做了(比如在活动或片段中),Dagger 将不知道你期望将依赖项注入到 SomeClass 中,并且它没有机会注入任何东西。

为了使其依赖项被注入,您也必须通过 Dagger 实例化(或注入)SomeClass 自身。

换句话说,在使用SomeClass 的活动中说,您需要:

@Inject
SomeClass mSomeClass;

接下来,您需要一个 Dagger 组件来执行实际的注入。要创建组件,您需要创建一个接口,该接口的方法将您的根对象(比如MainActivity)作为参数,例如:

@Singleton
@Component
public interface Bootstrap {
    void initialize(MainActivity activity);
}

现在,当您构建项目时,Dagger 2 会生成一个名为 DaggerBootstrap 的类,该类实现了此接口。你使用这个生成的类来执行注入,比如在你的活动的 onCreate 中:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    DaggerBootstrap.create().initialize(this);

    mSomeClass.doSomething();
}

我相信这个生成的组件是您缺少的关键部分。以上here的完整代码。

一些有用的 Dagger 2 资源:

更新 2:

似乎最后一个缺失的部分是您的组件为Activity 基类提供了注入方法,但没有为您的实际具体活动提供注入方法。

不幸的是,Dagger 2 需要为 每个 活动或您要注入的其他类的注入方法。

正如您所提到的,当您的应用中有许多不同的活动时,这会很烦人。有一些可能的解决方法,搜索“dagger 2 injection base class”,例如@EpicPandaForce的这个建议:Dagger 2 base class injections

还请注意,正如 @EpicPandaForce 在 cmets 中指出的那样,在我的简单示例中,我每次都调用 DaggerLibraryComponent.create(),这可能不是您想要的,因为该组件应该提供您的单例,所以您是最好从其他地方(例如您的应用程序实例)获取现有实例。

【讨论】:

  • 非常感谢您花时间回答这个问题!我会尽快检查它,但我只是想让你知道我没有 Dagger 意义上的组件,而是“组件设计”意义上的组件——所以当我说“组件”时——那些与dagger ;) - 我所有与 dagger 相关的代码都在我的 OP 中(即什么都没有,它都在那里,当涉及到 dagger 时,这就是我所拥有的)
  • 我注意到一件事-您说“(或者您也可以将 MainComponent 设为单例并完全摆脱 LibraryComponent ”-但这不正是我在 OP 中所做的吗?MyManager 是注释为@Singleton - 还是你在我的 OP 配置中看到错误? MyManager 用于我的库项目和我的实际应用程序中 - 所以我必须将它注入到驻留在两个项目中的类和活动中(库和应用程序)-我在 OP 中设置的方式似乎不起作用
  • 感谢@Zainodis 的澄清!所以你的项目中没有@Component接口的接口?那一定是问题所在......我会更新我的答案以进一步扩展,希望它有意义! :)
  • 完全正确 - 没有 @Component 接口 :) - 我觉得我遗漏了一些东西,但对我来说,从 dagger2 文档中不知何故,@ Singleton 需要 @ Component 在某处用作好 - 期待您的更新,非常感谢您抽出时间提供帮助 :)!
  • 不客气,我自己也在学习这些东西,所以我很高兴为目前 Android 中 DI 资源的缺乏做出贡献!我希望更新有所帮助!
猜你喜欢
  • 2023-04-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-06-14
相关资源
最近更新 更多