Dagger-我们是不是应该为每个活动/片段创建每个组件和模块

Posted

技术标签:

【中文标题】Dagger-我们是不是应该为每个活动/片段创建每个组件和模块【英文标题】:Dagger- Should we create each component and module for each Activity/ FragmentDagger-我们是否应该为每个活动/片段创建每个组件和模块 【发布时间】:2016-07-12 10:40:52 【问题描述】:

我使用 dagger2 已经有一段时间了。我很困惑是否为每个活动/片段创建自己的组件/模块。请帮我澄清一下:

例如,我们有一个应用程序,该应用程序有大约 50 个屏幕。 我们将按照 MVP 模式和适用于 DI 的 Dagger2 来实现代码。假设我们有 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, ....)
    
    
    

    创建活动范围:

    @Scope
    @Documented
    @Retention(value=RUNTIME)
    public @interface ActivityScope 
    
    

    为每个活动创建组件和模块。通常我会将它们作为静态类放在 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, ....)
    
    
    

他的解释是:他不必为每个活动创建组件和模块。 我认为我朋友的想法绝对不好,但如果我错了,请纠正我。原因如下:

    大量内存泄漏

    即使用户只打开了 2 个活动,该应用也会创建 50 个演示者。 用户关闭 Activity 后,其演示者仍将保留

    如果我想创建一个 Activity 的两个实例会怎样? (他怎么能创建两个演示者)

    应用初始化需要很长时间(因为它必须创建许多演示者、对象……)

很抱歉发了这么长的帖子,但请帮我为我和我的朋友澄清一下,我无法说服他。 您的 cmets 将不胜感激。

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

进行演示后进行编辑。

首先,感谢@pandawarrior 的回答。 在我问这个问题之前,我应该创建一个演示。我希望我在这里的结论可以帮助其他人。

    我的朋友所做的不会导致内存泄漏,除非他将任何 Scope 放入 Provides 方法。 (例如@Singleton 或@UserScope,...) 如果 Provides 方法没有任何范围,我们可以创建许多演示者。 (所以,我的第二点也是错误的) Dagger 只会在需要时创建演示者。 (所以,应用程序不会需要很长时间来初始化,我被惰性注入搞糊涂了)

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

    当他在模块/组件中初始化所有演示者时,这对源的架构不利。 (它违反了Interface segregation principle,也可能违反了Single Responsibility 原则)。

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

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

希望阅读更多内容: 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 将稍后运行,您可能会得到错误的结果。

【问题讨论】:

【参考方案1】:

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

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

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

因此,每个Activity 的组件是多余的,但是整个应用程序的单个组件不够灵活。最佳解决方案介于这两个极端之间(通常是这样)。

我使用以下方法:

    提供“全局”对象的单个“应用程序”组件(例如,保持全局状态的对象,在应用程序中的所有组件之间共享)。在Application 中实例化。 “应用程序”组件的“控制器”子组件提供所有面向用户的“控制器”所​​需的对象(在我的架构中,这些对象是ActivitiesFragments)。在每个 ActivityFragment 中实例化。 “应用程序”组件的“服务”子组件,提供所有Services 所需的对象。在每个Service 中实例化。

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


2017 年 7 月编辑

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


2018 年 2 月编辑

我发布了complete course about dependency injection in Android。

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

如果您参加本课程,您就会明白为什么为每个 Activity/Fragment 单独定义模块/组件的想法在最基本的方面存在缺陷。

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


适用范围:

@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 Scopes Demystified

Dependency Injection in Android

【讨论】:

感谢@vasiliy 分享您的意见。这正是我将如何使用它并且目前遵循的策略。在 MVP 模式的情况下,引用的ControllerModule 将创建一个新的Presenter,然后将演示者注入ActivityFragment。有什么支持或反对的可靠意见吗? @Vasiliy,我阅读了您的整篇文章,发现您可能没有在机制中考虑 interactorspresenters。 ControllerModule 会提供 interactorspresenters 的所有依赖吗?如果我错过了什么,请给一个小提示。 @mahbub.kuet,如果我理解您所指的“交互者”和“演示者”,ControllerComponent 应该注入它们。是否将它们连接到ControllerModule 内,或者引入其他模块取决于您。在实际应用程序中,我建议使用每个组件的多模块方法,而不是将所有内容放在单个模块中。这是ApplicationComponent 的示例,但控制器将是相同的:github.com/techyourchance/idocare-android/tree/master/app/src/… @Mr.Hyde,一般来说是的,但是你必须在ApplicationComponent 中明确声明ControllerComponent 可以使用的所有依赖项。生成代码的方法计数也会更高。我还没有找到使用依赖组件的好理由。 我今天在我的所有项目中都使用这种方法,并且我明确不使用 dagger.android 包中的任何东西,因为我发现它没有动力。因此,这个示例仍然是最新的,并且仍然是在 Android 恕我直言中执行 DI 的最佳方法。【参考方案2】:

使用Provider<"your component's name"> 而不是简单的组件实现,以避免内存泄漏和创建大量无用组件。因此,当您调用 get() 方法时,您的组件将由惰性创建,因为您不提供组件实例,而只提供提供程序。因此,如果调用了提供者的 .get(),则将应用您的演示者。在此处阅读有关提供者的信息并应用它。 (Official Dagger documentation)


另外一个很好的方法是使用多重绑定。根据它,您应该将您的演示者绑定到地图中,并在需要时通过提供者创建它们。 (here is docs about multibinding)

【讨论】:

【参考方案3】:

可以在 Google Android 架构蓝图 Github 存储库 here 中找到有关如何组织组件、模块和包的一些最佳示例。

如果您检查那里的源代码,您会看到有一个应用程序范围的组件(具有整个应用程序持续时间的生命周期),然后为对应于给定的 Activity 和 Fragment 的单独的 Activity 范围的组件项目中的功能。例如,有以下包:

addedittask
taskdetail
tasks

每个包里面都有一个模块、组件、presenter等。例如taskdetail里面有以下类:

TaskDetailActivity.java
TaskDetailComponent.java
TaskDetailContract.java
TaskDetailFragment.java
TaskDetailPresenter.java
TaskDetailPresenterModule.java

以这种方式组织(而不是将所有活动分组在一个组件或模块中)的优点是您可以利用 Java 可访问性修饰符并实现有效 Java 条款 13。换句话说,功能分组的类将是在同一个包中,您可以利用 protectedpackage-private accessibility modifiers 来防止意外使用您的类。

【讨论】:

这也是我的首选方法。我不喜欢活动/片段访问他们不应该访问的东西。【参考方案4】:

第一个选项为每个活动创建一个子范围组件,其中活动能够创建仅提供该特定活动的依赖项(演示者)的子范围组件。

第二个选项创建一个单独的@Singleton 组件,该组件能够将演示者作为无范围的依赖项提供,这意味着当您访问它们时,您每次都会创建一个新的演示者实例。 (不,在您请求之前它不会创建新实例)。


从技术上讲,两种方法都不比另一种差。第一种方法不是按功能划分演示者,而是按层划分。

我都用过,它们都有效,而且都有意义。

第一个解决方案的唯一缺点(如果你使用@Component(dependencies=...而不是@Subcomponent)是你需要确保它不是在内部创建自己的模块的Activity,因为那样你就不能替换模块方法使用模拟实现。再说一次,如果你使用构造函数注入而不是字段注入,你可以直接用构造函数创建类,直接给它模拟。

【讨论】:

【参考方案5】:

您的朋友是对的,您实际上不必为每个活动创建组件和模块。 Dagger 应该通过将类实例化委托给模块而不是在活动的 onCreate 方法中实例化它们来帮助您减少混乱的代码并使您的 Android 活动更清洁。

通常我们会这样做

public class MainActivity extends AppCompatActivity 


Presenter1 mPresenter1;

@Override
protected void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mPresenter1 = new Presenter1(); // you instantiate mPresentation1 in onCreate, imagine if there are 5, 10, 20... of objects for you to instantiate.



你可以这样做

public class MainActivity extends AppCompatActivity 

@Inject
Presenter1 mPresenter1; // the Dagger module take cares of instantiation for your

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


private void injectThisActivity() 
    MainApplication.get(this)
            .getMainComponent()
            .inject(this);

所以写太多东西有点打败匕首的目的不是吗?如果我必须为每个活动创建模块和组件,我宁愿在活动中实例化我的演示者。

关于您的问题:

1- 内存泄漏:

不,除非您为您提供的演示者添加@Singleton 注释。只有当您在目标类中执行@Inject 时,Dagger 才会创建对象。它不会在您的场景中创建其他演示者。您可以尝试使用 Log 来查看它们是否已创建。

@Module
public class AppPresenterModule 

@Provides
@Singleton // <-- this will persists throughout the application, too many of these is not good
Activity1Presenter provideActivity1Presentor(Context context, ...some other params)
    Log.d("Activity1Presenter", "Activity1Presenter initiated");
    return new Activity1PresenterImpl(context, ...some other params);


@Provides // Activity2Presenter will be provided every time you @Inject into the activity
Activity2Presenter provideActivity2Presentor(Context context, ...some other params)
    Log.d("Activity2Presenter", "Activity2Presenter initiated");
    return new Activity2PresenterImpl(context, ...some other params);


.... Same with 48 others presenters.

2- 你注入两次并记录他们的哈希码

//MainActivity.java
@Inject Activity1Presenter mPresentation1
@Inject Activity1Presenter mPresentation2

@Inject Activity2Presenter mPresentation3
@Inject Activity2Presenter mPresentation4
//log will show Presentation2 being initiated twice

@Override
protected void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    injectThisActivity();
    Log.d("Activity1Presenter1", mPresentation1.hashCode());
    Log.d("Activity1Presenter2", mPresentation2.hashCode());
    //it will shows that both have same hash, it's a Singleton
    Log.d("Activity2Presenter1", mPresentation3.hashCode());
    Log.d("Activity2Presenter2", mPresentation4.hashCode());
    //it will shows that both have different hash, hence different objects

3。不,对象只会在您 @Inject 进入活动时创建,而不是应用程序初始化。

【讨论】:

感谢您的评论,您说的没有错,但我认为这不是最佳答案,请参阅我的编辑帖子。因此,无法将其标记为已接受。 @EpicPandaForce:嗯,但你必须在某个地方实例化它。有些东西必须违反依赖倒置原则。

以上是关于Dagger-我们是不是应该为每个活动/片段创建每个组件和模块的主要内容,如果未能解决你的问题,请参考以下文章

我应该用片段替换 Android 活动吗?

实现一个可以从每个片段和活动中访问的按钮

开发具有多个活动或一个活动和多个片段的应用程序?

Android开发——UI_片段

我们可以在活动 xml 中编写 UI 以及在片段 xm 中编写 UI 吗?

使用工具栏中的按钮在片段之间导航