草庐IT

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

coder 2023-05-07 原文

我使用 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/

    有关android - Dagger- 我们应该为每个 Activity/fragment 创建每个组件和模块吗?的更多相关文章

    1. ruby - 在 Ruby 中使用匿名模块 - 2

      假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

    2. ruby - 如何在 Ruby 中顺序创建 PI - 2

      出于纯粹的兴趣,我很好奇如何按顺序创建PI,而不是在过程结果之后生成数字,而是让数字在过程本身生成时显示。如果是这种情况,那么数字可以自行产生,我可以对以前看到的数字实现垃圾收集,从而创建一个无限系列。结果只是在Pi系列之后每秒生成一个数字。这是我通过互联网筛选的结果:这是流行的计算机友好算法,类机器算法:defarccot(x,unity)xpow=unity/xn=1sign=1sum=0loopdoterm=xpow/nbreakifterm==0sum+=sign*(xpow/n)xpow/=x*xn+=2sign=-signendsumenddefcalc_pi(digits

    3. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

      作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

    4. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

      关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

    5. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

      使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

    6. ruby - 检查 "command"的输出应该包含 NilClass 的意外崩溃 - 2

      为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar

    7. ruby-on-rails - 无法使用 Rails 3.2 创建插件? - 2

      我对最新版本的Rails有疑问。我创建了一个新应用程序(railsnewMyProject),但我没有脚本/生成,只有脚本/rails,当我输入ruby./script/railsgeneratepluginmy_plugin"Couldnotfindgeneratorplugin.".你知道如何生成插件模板吗?没有这个命令可以创建插件吗?PS:我正在使用Rails3.2.1和ruby​​1.8.7[universal-darwin11.0] 最佳答案 随着Rails3.2.0的发布,插件生成器已经被移除。查看变更日志here.现在

    8. ruby-on-rails - 在混合/模块中覆盖模型的属性访问器 - 2

      我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah

    9. ruby - 如何使用 RSpec::Core::RakeTask 创建 RSpec Rake 任务? - 2

      如何使用RSpec::Core::RakeTask初始化RSpecRake任务?require'rspec/core/rake_task'RSpec::Core::RakeTask.newdo|t|#whatdoIputinhere?endInitialize函数记录在http://rubydoc.info/github/rspec/rspec-core/RSpec/Core/RakeTask#initialize-instance_method没有很好的记录;它只是说:-(RakeTask)initialize(*args,&task_block)AnewinstanceofRake

    10. ruby - 为什么 SecureRandom.uuid 创建一个唯一的字符串? - 2

      关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?

    随机推荐