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


85

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

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

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

  1. 创建一个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, ....)
    
    }
    
  2. 创建ActivityScope:

    @Scope
    @Documented
    @Retention(value=RUNTIME)
    public @interface ActivityScope {
    }
    
  3. 为每个活动创建组件和模块。通常,我会将它们作为静态类放入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.
    

这些只是非常简单的示例,以显示我将如何实现这一点。

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

  1. 创建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.
    
    }
    
  2. 创建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, ....)
    
    }
    

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

  1. 很多内存泄漏

    • 即使用户仅打开两个活动,该应用程序仍将创建50个演示者。
    • 用户关闭活动后,其演示者仍将保留
  2. 如果我想为一个活动创建两个实例,该怎么办?(他如何创建两个演示者)

  3. 应用初始化需要花费很多时间(因为它必须创建许多演示者,对象等)。

抱歉,很长的帖子,但是请帮助我为我和我的朋友澄清一下,我无法说服他。 您的意见将不胜感激。

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

演示后进行编辑。

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

  1. 我的朋友所做的事情不会导致内存泄漏,除非他将任何范围都应用于Provides方法。(例如,@ Singleton或@UserScope,...)
  2. 如果Provides方法没有任何作用域,我们可以创建许多演示者。(所以,我的第二点也是错误的)
  3. Dagger仅在需要时创建演示者。(因此,该应用将不需要很长时间来初始化,我被懒惰注入所迷惑)

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

  1. 当他将模块/组件中的所有演示者初始化时,对源代码的体系结构不利。(它也违反了接口隔离原则,也许也违反了单一职责原则)。

  2. 创建作用域组件时,我们将知道它的创建时间和销毁时间,这对于避免内存泄漏具有巨大的好处。因此,对于每个活动,我们都应该使用@ActivityScope创建一个组件。想象一下,在我的朋友实现中,我们忘记在Provider方法中放置一些Scope =>将会发生内存泄漏。

我认为,对于一个小型应用程序(只有几个没有很多依赖性或类似依赖性的屏幕),我们可以应用我的朋友的想法,但是当然不建议这样做。

首选阅读更多内容: 是什么决定了Dagger 2中组件(对象图)的生命周期? Dagger2活动范围,我需要多少个模块/组件?

还有一点要注意:如果要查看对象何时被销毁,可以一起调用方法的对象,GC将立即运行:

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

如果仅使用这些方法之一,GC将在以后运行,并且可能会得到错误的结果。

Answers:


85

为每个声明一个单独的模块Activity根本不是一个好主意。为每个元素声明单独的组件Activity甚至更糟。这背后的原因很简单-您实际上并不需要所有这些模块/组件(就像您自己已经看到的那样)。

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

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

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

我使用以下方法:

  1. 提供“全局”对象的单个“应用程序”组件(例如,拥有在应用程序中所有组件之间共享的全局状态的对象)。在中实例化Application
  2. “应用程序”组件的“控制器”子组件,提供所有面向用户的“控制器”所​​需的对象(在我的体系结构中为ActivitiesFragments)。在Activity和中实例化Fragment
  3. “应用程序”组件的“服务”子组件,它提供all所需的对象Services。在每个实例化Service

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


编辑2017年7月

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


编辑2018年2月

在Android上发布了有关依赖注入完整课程

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

如果您学习这门课程,您将会理解为什么为每个活动/片段分别定义模块/组件的定义在最根本的方式上存在缺陷。

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


适用范围:

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

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

神秘的匕首2范围

Android中的依赖注入


1
感谢@vasiliy分享您的意见。这正是我将如何使用它以及当前遵循的策略。如果是MVP模式,则推荐人ControllerModule将创建一个新人Presenter,然后将主持人插入Activity或中Fragment。任何赞成或反对的意见?
Wahib Ul Haq'3

@Vasiliy,我阅读了整篇文章,发现也许您没有考虑机制中的交互者演示者。ControllerModule将提供交互者演示者的所有依赖吗?如果我错过了任何事情,请给个小提示。
iamcrypticcoder

@ mahbub.kuet,如果我理解您所说的“交互者”和“演示者”所指的内容,ControllerComponent则应注入它们。是否将它们连接到内部ControllerModule,还是引入其他模块由您决定。在实际应用中,我建议使用每个组件多模块的方法,而不是将所有内容都放在一个模块中。下面是一个例子ApplicationComponent,但控制器的将是相同的:github.com/techyourchance/idocare-android/tree/master/app/src/...
瓦西里

2
@ Mr.Hyde,通常是,但是您必须在ApplicationComponent所有ControllerComponent可以使用的依赖项中明确声明。同样,所生成代码的方法计数将更高。我还没有找到使用依赖组件的充分理由。
Vasiliy

1
我今天在所有项目中都使用了这种方法,但我明确不使用dagger.android软件包中的任何东西,因为我发现这样做动机不足。因此,该示例仍是最新的,并且仍然是在Android IMHO中进行DI的最佳方法。
瓦西里(Vasiliy)'18年

15

有关如何组织组件,模块和包的一些最佳示例,请参见此处的Google Android体系结构蓝图Github存储库。

如果您在此处查看源代码,则可以看到只有一个应用程序范围的组件(其生命周期为整个应用程序的生命周期),然后为Activity和Fragment分别与Activity片段中的给定功能相对应的Activity范围的组件进行了分离。项目。例如,有以下软件包:

addedittask
taskdetail
tasks

每个软件包中都有一个模块,组件,演示者等。例如,内部taskdetail有以下类:

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

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


1
这也是我的首选方法。我不喜欢活动/片段可以访问他们不应该访问的内容。
Joao Sousa

3

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

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


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

我都用过,它们都起作用并且都有意义。

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


1

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


另一种很棒的方法是使用多重绑定。按照它,您应该将演示者绑定到地图中,并在需要时通过提供者创建它们。(这是有关多重绑定的文档


-5

您的朋友是正确的,您实际上不必为每个活动都创建组件和模块。Dagger应该通过将类实例委托给Modules而不是在Activity的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进入活动时创建对象,而不是在应用程序初始化时创建对象。


1
感谢您的评论,您的发言没有错,但我认为这不是最佳答案,请参阅我的编辑文章。因此,无法将其标记为已接受。
Mike Mike

@EpicPandaForce:嗯,但是您必须在某个地方实例化它。某些东西将不得不违反依赖倒置原则。
David Liu
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.