当前位置: 首页 > 知识库问答 >
问题:

Dagger-我们应该为每个activity/片段创建每个组件和模块吗

蒙胤
2023-03-14

我和dagger2合作有一段时间了。而且我也搞不清楚要不要为每个activity/片段创建一个自己的组件/模块。请帮我澄清一下:

比如我们有一个app,这个app大概有50个屏幕。我们将按照MVP模式和Dagger2为DI实现代码。假设我们有50个活动和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, ....)
    
    }
    

    创建ActivityScope:

    @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.
    

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

    但我的一个朋友刚刚给了我另一个实现:

    >

  • 创建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创建组件和模块。我觉得我朋友的想法绝对一点也不好,但是如果我错了请纠正我。原因如下:

    >

  • 大量内存泄漏:

    • 即使用户只有两个活动打开,应用程序也会创建50个演示者。
    • 用户关闭activity后,其演示者仍将保留

    如果我想创建一个activity的两个实例,会发生什么?(他如何创建两个演示者)

    应用程序初始化会花费很多时间(因为它要创建很多演示者、对象、...)

    抱歉的一个很长的帖子,但请帮助我澄清这为我和我的朋友,我无法说服他。您的意见将非常感谢。

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

    编辑后做一个演示。

    首先,感谢@Pandawarrior回答。我应该在问这个问题之前创建一个演示。我希望我的结论能对其他人有所帮助。

    1. 我的朋友所做的不会导致内存泄漏,除非他对provides-methods设置任何作用域。(例如@singleton或@userscope,...)
    2. 如果Provides-method没有任何作用域,我们可以创建许多演示者。(所以,我的第二点也是错误的)
    3. Dagger将只在需要时创建演示者。(所以,app初始化不会花很长时间,我被懒惰注入搞糊涂了)

    所以,以上我所说的理由大多都是错误的。但这并不意味着我们应该遵循我朋友的想法,原因有二:

    >

  • 当源将所有演示者都放在模块/组件中时,这对源的体系结构不利。(它违反了界面分离原则,也可能是单一责任原则)。

    当我们创建一个作用域组件时,我们将知道它何时被创建,何时被销毁,这对于避免内存泄漏是一个巨大的好处。因此,对于每个activity,我们都应该创建一个带有@ActivityScope的组件。让我们想象一下,在my friends实现中,我们忘记在Provider中放置一些作用域-method=>内存泄漏将会发生。

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

    更喜欢阅读更多关于:是什么决定了Dagger2中组件(对象图)的生命周期?Dagger2 activity范围,我需要多少模块/组件?

    还有一个注意事项:如果您想查看对象何时被销毁,可以一起调用方法的对象,那么GC将立即运行:

        System.runFinalization();
        System.gc();
    

    如果您只使用这些方法中的一种,GC将稍后运行,您可能会得到错误的结果。

  • 共有1个答案

    经慈
    2023-03-14

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

    但是,只有一个组件与应用程序的生命周期绑定,并将其注入所有活动也不是最佳解决方案(这是您朋友的方法)。它不是最佳的,因为:

    1. 它将您限制在一个作用域(@singleton或自定义作用域)
    2. 您被限制的唯一作用域使注入的对象成为“应用程序单例”,因此作用域中的错误或作用域对象的不正确使用很容易导致全局内存泄漏
    3. 您也希望使用Dagger2注入服务中,但是服务可能需要与活动不同的对象(例如,服务不需要演示者,不具有FragmentManager等)。通过使用单个组件,您就可以放松为不同组件定义不同对象图的灵活性。

    因此,每个activity都有一个组件是矫枉过正的,但对于整个应用程序来说,单个组件不够灵活。最优解介于这两个极端之间(通常如此)。

    我使用以下方法:

    1. 提供“全局”对象的单个“应用程序”组件(例如,保存全局状态的对象,该全局状态在应用程序中的所有组件之间共享)。在应用程序中实例化。
    2. “Controller”是“Application”组件的子组件,它提供所有面向用户的“Controller”所需的对象(在我的体系结构中,这些对象是活动片段)。在每个activity片段中实例化。
    3. “应用程序”组件的
    4. “服务”子组件,提供所有服务所需的对象。在每个服务中实例化。

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

    编辑2017年7月

    我发布了一个视频教程,展示了如何在Android应用中构造Dagger依赖注入代码:Android Dagger for professional教程。

    2018年2月编辑

    我发表了一个关于Android中依赖注入的完整课程。

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

    如果您学习了这门课程,您就会明白为什么为每个activity/片段单独定义模块/组件的想法在最根本的方面是有缺陷的。

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

    适用范围:

    @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);
        }
    }
    

    控制器作用域:

    @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;
        }
    }
    

    有关依赖注入的其他信息:

    Dagger 2镜

    Android中的依赖注入

     类似资料:
    • 问题内容: 著名的Angular App Structure最佳实践推荐 博客文章概述了新推荐的angularjs项目结构,该项目结构现在是面向组件而非功能的,或者在最初的github问题中命名为“ Organized by Feature”。 博客文章建议每个模块中的每个文件应以模块名称开头,例如: 问题是:与在功能上命名文件相反,在模块的每个文件名内重复模块名称有什么好处和坏处?例如: 我问的

    • 问题内容: 我正在看下面的代码片段: 从这里:http : //howtonode.org/node-redis- fun 。 我不太了解发生了什么。从示例中,我认为Redis客户端是数据库和程序员之间的某种接口,但现在看来他们正在为每个提交的代码创建一个新客户端(他们在教程中构建的应用程序接受代码段)提交并将其存储在数据库中)! 另外,Redis数据库存储在哪里?与脚本位于同一目录中吗?我该如何

    • 我有数据。下面的框架。我想添加一列“g”,它根据列中的连续序列对数据进行分类。也就是说,如最后一列“g”所示,h_no

    • 有没有一种方法,每当I$push monodb数组中的新元素时,都会向它添加一个普通的_id?我记得mongoose是自动执行类似操作的,但现在我使用mongodb的原生js,它似乎没有插入任何id。 示例: 执行时,messages数组应具有常规的_id字段、message和date。目前它只创建消息和日期。

    • 问题内容: 在一些示例之后,似乎我们可以注入一个工厂,其中包含一个REST服务的终结点,如下所示 这看起来不错,但可以想象我还有其他端点,即/ users /:id和/ groups /:id,因为您可以想象到不同端点的数量将会增加。 因此,对于每个终结点,都有一个不同的工厂,这是一个好习惯。 还是有另一种推荐的方法? 我确实没有发现任何问题,但是它迫使我创建许多工厂来处理不同的端点。 确实需要任