android - Dagger- 我们应该为每个 Activity/fragment 创建每个组件和模块吗?

标签 android dagger dagger-2

我使用 dagger2 已经有一段时间了。我对为每个 Activity/fragment 创建自己的组件/模块感到困惑。请帮我澄清一下:

例如,我们有一个应用程序,该应用程序大约有 50 个屏幕。
我们将按照 MVP 模式和 Dagger2 为 DI 实现代码。假设我们有 50 个 Activity 和 50 个演示者。

在我看来,通常我们应该这样组织代码:

  • 创建一个 AppComponent 和 AppModule,它们将提供在应用程序打开时将使用的所有对象。
    @Module
    public class AppModule {
    
        private final MyApplicationClass application;
    
        public AppModule(MyApplicationClass application) {
            this.application = application;
        }
    
        @Provides
        @Singleton
        Context provideApplicationContext() {
            return this.application;
        }
    
        //... and many other providers 
    
    }
    
    @Singleton
    @Component( modules = { AppModule.class } )
    public interface AppComponent {
    
        Context getAppContext();
    
        Activity1Component plus(Activity1Module module);
        Activity2Component plus(Activity2Module module);
    
        //... plus 48 methods for 48 other activities. Suppose that we don't have any other Scope (like UserScope after user login, ....)
    
    }
    
  • 创建 Activity 范围:
    @Scope
    @Documented
    @Retention(value=RUNTIME)
    public @interface ActivityScope {
    }
    
  • 为每个 Activity 创建组件和模块。通常我会将它们作为静态类放在 Activity 类中:
    @Module
    public class Activity1Module {
    
        public LoginModule() {
        }
        @Provides
        @ActivityScope
        Activity1Presenter provideActivity1Presenter(Context context, /*...some other params*/){
            return new Activity1PresenterImpl(context, /*...some other params*/);
        }
    
    }
    
    @ActivityScope
    @Subcomponent( modules = { Activity1Module.class } )
    public interface Activity1Component {
        void inject(Activity1 activity); // inject Presenter to the Activity
    }
    
    // .... Same with 49 remaining modules and components.
    

  • 这些只是非常简单的例子来展示我将如何实现这一点。

    但是我的一个 friend 刚刚给了我另一个实现:
  • 创建 PresenterModule 它将提供所有演示者:
    @Module
    public class AppPresenterModule {
    
        @Provides
        Activity1Presenter provideActivity1Presentor(Context context, /*...some other params*/){
            return new Activity1PresenterImpl(context, /*...some other params*/);
        }
    
        @Provides
        Activity2Presenter provideActivity2Presentor(Context context, /*...some other params*/){
            return new Activity2PresenterImpl(context, /*...some other params*/);
        }
    
        //... same with 48 other presenters.
    
    }
    
  • 创建 AppModule 和 AppComponent:
    @Module
    public class AppModule {
    
        private final MyApplicationClass application;
    
        public AppModule(MyApplicationClass application) {
            this.application = application;
        }
    
        @Provides
        @Singleton
        Context provideApplicationContext() {
            return this.application;
        }
    
        //... and many other provides 
    
    }
    
    @Singleton
    @Component(
            modules = { AppModule.class,  AppPresenterModule.class }
    )
    public interface AppComponent {
    
        Context getAppContext();
    
        public void inject(Activity1 activity);
        public void inject(Activity2 activity);
    
        //... and 48 other methods for 48 other activities. Suppose that we don't have any other Scope (like UserScope after user login, ....)
    
    }
    

  • 他的解释是:他不必为每个 Activity 创建组件和模块。
    我觉得我 friend 的想法绝对不好,但如果我错了,请纠正我。原因如下:
  • 大量内存泄漏 :
  • 即使用户只打开了 2 个 Activity ,该应用程序也会创建 50 个演示者。
  • 用户关闭一个 Activity 后,它的 Presenter 仍然是
  • 如果我想创建一个 Activity 的两个实例会发生什么? (他怎么能创建两个演示者)
  • 应用程序初始化需要很多时间(因为它必须创建许多演示者、对象……)

  • 很抱歉发表了很长的帖子,但请帮我为我和我的 friend 澄清这一点,我无法说服他。
    您的意见将不胜感激。

    /------------------------------------------------- ---------------/

    做一个演示后编辑。

    首先,感谢@pandawarrior 的回答。
    在问这个问题之前,我应该创建一个演示。我希望我在这里的结论可以帮助别人。
  • 我的 friend 所做的不会导致内存泄漏,除非他将任何 Scope 放入 Provides-methods。 (例如@Singleton,或@UserScope,...)
  • 如果提供方法没有任何范围,我们可以创建许多演示者。 (所以,我的第二点也错了)
  • Dagger 只会在需要时创建演示者。 (所以,应用程序不会花很长时间初始化,我被懒惰的注入(inject)搞糊涂了)

  • 所以,我上面说的所有理由大多是错误的。但这并不意味着我们应该遵循我 friend 的想法,原因有二:
  • 当他在模块/组件中初始化所有演示者时,这对源的架构不利。 (它也违反了 Interface segregation principle ,也可能违反了 Single Responsibility 原则)。
  • 当我们创建一个 Scope 组件时,我们会知道它什么时候创建,什么时候销毁,这对于避免内存泄漏是一个巨大的好处。因此,对于每个 Activity,我们应该创建一个带有 @ActivityScope 的组件。让我们想象一下,在我 friend 的实现中,我们忘记在 Provider-method 中放置一些 Scope => 会发生内存泄漏。

  • 在我看来,用一个小应用程序(只有几个屏幕,没有很多依赖或类似的依赖),我们可以应用我 friend 的想法,但当然不推荐。

    更喜欢阅读以下内容:
    What determines the lifecycle of a component (object graph) in Dagger 2?
    Dagger2 activity scope, how many modules/components do i need?

    还有一点要注意:如果你想看看对象什么时候被销毁,你可以一起调用方法的那些,GC会立即运行:
        System.runFinalization();
        System.gc();
    

    如果你只使用其中一种方法,GC 会运行得更晚,你可能会得到错误的结果。

    最佳答案

    为每个 Activity 声明一个单独的模块根本不是一个好主意。为每个 Activity 声明单独的组件更糟。这背后的原因很简单——您并不真正需要所有这些模块/组件(正如您自己已经看到的那样)。

    但是,只有一个与 Application 相关联的组件的生命周期并将其用于注入(inject)所有 Activities也不是最佳解决方案(这是您 friend 的方法)。它不是最佳的,因为:

  • 它限制你只有一个范围( @Singleton 或自定义范围)
  • 您被限制的唯一范围使注入(inject)的对象成为“应用程序单例”,因此范围对象的错误使用或不正确的使用很容易导致全局内存泄漏
  • 您需要使用 Dagger2 来注入(inject) Services也是,但是 Services可以需要与 Activities 不同的对象(例如 Services 不需要演示者,不需要 FragmentManager 等)。通过使用单个组件,您失去了为不同组件定义不同对象图的灵活性。

  • 因此,每个 Activity 的一个组件是一种矫枉过正,但整个应用程序的单个组件不够灵活。最佳解决方案介于这些极端之间(通常如此)。

    我使用以下方法:
  • 提供“全局”对象的单个“应用程序”组件(例如,保存在应用程序中所有组件之间共享的全局状态的对象)。实例化于 Application .
  • “应用程序”组件的“ Controller ”子组件提供所有面向用户的“ Controller ”所​​需的对象(在我的架构中,这些是 ActivitiesFragments )。在每个 Activity 中实例化和 Fragment .
  • “应用程序”组件的“服务”子组件,提供所有 Services 所需的对象.在每个 Service 中实例化.

  • 以下是如何实现相同方法的示例。

    2017 年 7 月编辑

    我发布了一个视频教程,展示了如何在 Android 应用程序中构建 Dagger 依赖注入(inject)代码:Android Dagger for Professionals Tutorial .

    2018 年 2 月编辑

    我发表了一篇 complete course about dependency injection in Android .

    在本类(class)中,我解释了依赖注入(inject)的理论,并展示了它是如何在 Android 应用程序中自然出现的。然后我演示了 Dagger 构造如何适应一般的依赖注入(inject)方案。

    如果您参加本类(class),您将理解为什么为每个 Activity/fragment 单独定义模块/组件的想法在最基本的方面基本上是有缺陷的。

    这种方法使表示层的结构从“功能”类集合镜像到“构造”类集合结构,从而将它们耦合在一起。这违背了依赖注入(inject)的主要目标,即保持类的“构造”和“功能”集不相交。

    适用范围:
    @ApplicationScope
    @Component(modules = ApplicationModule.class)
    public interface ApplicationComponent {
    
        // Each subcomponent can depend on more than one module
        ControllerComponent newControllerComponent(ControllerModule module);
        ServiceComponent newServiceComponent(ServiceModule module);
    
    }
    
    
    @Module
    public class ApplicationModule {
    
        private final Application mApplication;
    
        public ApplicationModule(Application application) {
            mApplication = application;
        }
    
        @Provides
        @ApplicationScope
        Application applicationContext() {
            return mApplication;
        }
    
        @Provides
        @ApplicationScope
        SharedPreferences sharedPreferences() {
            return mApplication.getSharedPreferences(Constants.PREFERENCES_FILE, Context.MODE_PRIVATE);
        }
    
        @Provides
        @ApplicationScope
        SettingsManager settingsManager(SharedPreferences sharedPreferences) {
            return new SettingsManager(sharedPreferences);
        }
    }
    

    Controller 范围:
    @ControllerScope
    @Subcomponent(modules = {ControllerModule.class})
    public interface ControllerComponent {
    
        void inject(CustomActivity customActivity); // add more activities if needed
    
        void inject(CustomFragment customFragment); // add more fragments if needed
    
        void inject(CustomDialogFragment customDialogFragment); // add more dialogs if needed
    
    }
    
    
    
    @Module
    public class ControllerModule {
    
        private Activity mActivity;
        private FragmentManager mFragmentManager;
    
        public ControllerModule(Activity activity, FragmentManager fragmentManager) {
            mActivity = activity;
            mFragmentManager = fragmentManager;
        }
    
        @Provides
        @ControllerScope
        Context context() {
            return mActivity;
        }
    
        @Provides
        @ControllerScope
        Activity activity() {
            return mActivity;
        }
    
        @Provides
        @ControllerScope
        DialogsManager dialogsManager(FragmentManager fragmentManager) {
            return new DialogsManager(fragmentManager);
        }
    
        // @Provides for presenters can be declared here, or in a standalone PresentersModule (which is better)
    }
    

    然后在 Activity :
    public class CustomActivity extends AppCompatActivity {
    
        @Inject DialogsManager mDialogsManager;
    
        private ControllerComponent mControllerComponent;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            getControllerComponent().inject(this);
    
        }
    
        private ControllerComponent getControllerComponent() {
            if (mControllerComponent == null) {
    
                mControllerComponent = ((MyApplication)getApplication()).getApplicationComponent()
                        .newControllerComponent(new ControllerModule(this, getSupportFragmentManager()));
            }
    
            return mControllerComponent;
        }
    }
    

    关于依赖注入(inject)的附加信息:

    Dagger 2 Scopes Demystified

    Dependency Injection in Android

    关于android - Dagger- 我们应该为每个 Activity/fragment 创建每个组件和模块吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36206989/

    相关文章:

    java - 解析数据时出错 org.json.JSONException : type java. lang.String 无法转换为 JSONObject

    java - 是否可以将子组件依赖注入(inject)到父组件中?

    android - Dagger2 - 在运行时定义/更新对象的值

    Android 库、Kotlin 和 Dagger2

    android - Dagger 2.10 Android 子组件和构建器

    android - 如何在 imageview android 中显示大尺寸位图?

    android - DocumentsContract.copyDocument() 总是失败

    android - 如何在git上添加proguard映射文件(在gitignore、Android Studio中排除)

    android - 使用支持 fragment 时的 fragmentDispatchingAndroidInjector

    android - Dagger 项目在 getApplication 覆盖时静默失败