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

时间:2016-03-24 18:01:06

标签: android dagger dagger-2

我已经和dagger2合作了一段时间。我为每个Activity / Fragment创建一个自己的组件/模块感到困惑。请帮我澄清一下:

例如,我们有一个应用程序,该应用程序有大约50个屏幕。 我们将实现遵循MVP模式的代码和用于DI的Dagger2。假设我们有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.
    
  4. 这些只是非常简单的例子,展示了我将如何实现这一点。

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

    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, ....)
      
      }
      
    3. 他的解释是:他不必为每项活动创建组件和模块。 我认为我的朋友的想法绝对不是很好,但如果我错了,请纠正我。原因如下:

      1. 大量内存泄漏

        • 即使用户只打开了2个活动,该应用也会创建50个演示者。
        • 用户关闭活动后,其展示者仍将
      2. 如果我想创建一个Activity的两个实例,会发生什么? (他怎么能创建两个演示者)

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

      4. 很抱歉很长的帖子,但请帮助我为我和我的朋友澄清一下,我无法说服他。 非常感谢您的评论。

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

        进行演示后编辑。

        首先,感谢@pandawarrior回答。 在我提出这个问题之前,我应该创建一个Demo。我希望我的结论可以帮助别人。

        1. 我的朋友所做的事情不会导致内存泄漏,除非他将任何Scope放入Provide-methods。 (例如@Singleton,或@UserScope,......)
        2. 如果Provide-method没有任何Scope,我们可以创建许多演示者。 (所以,我的第二点也错了)
        3. Dagger只在需要时才会创建演示者。 (因此,应用程序不会花费很长时间进行初始化,我对Lazy Injection感到困惑)
        4. 所以,我上面提到的所有原因大多都是错误的。但这并不意味着我们应该遵循我的朋友的想法,原因有两个:

          1. 当他进入模块/组件中的所有演示者时,它对源的体系结构不利。 (它也违反Interface segregation principle,也许Single Responsibility原则。

          2. 当我们创建一个Scope Component时,我们将知道它何时被创建以及它何时被销毁,这对于避免内存泄漏是一个巨大的好处。因此,对于每个Activity,我们应该使用@ActivityScope创建一个Component。让我们想象一下,在朋友实施的情况下,我们忘记在Provider-method =>中放入一些Scope。将发生内存泄漏。

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

            更喜欢阅读: 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将在稍后运行,您可能会得到错误的结果。

5 个答案:

答案 0 :(得分:70)

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

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

  1. 它将您限制为仅限一个范围(@Singleton或自定义范围)
  2. 您限制使用的唯一范围是注入的对象"应用程序单例",因此范围内的错误或作用域对象的错误使用很容易导致全局内存泄漏
  3. 您也希望使用Dagger2注入Services,但Services可能需要与Activities不同的对象(例如Services don&#39 ; t需要演示者,不要FragmentManager等。通过使用单个组件,您可以灵活地为不同的组件定义不同的对象图。
  4. 因此,每个Activity的组件是一种过度杀伤,但整个应用程序的单个组件不够灵活。最佳解决方案介于这两个极端之间(通常是这样)。

    我使用以下方法:

    1. Single" application"提供"全球"的组件对象(例如,保持全局状态的对象,在应用程序中的所有组件之间共享)。在Application
    2. 中实例化
    3. "控制器" '#34; application"的子组件提供所有面向用户的控制器所需对象的组件" (在我的架构中,这些是ActivitiesFragments)。在每个ActivityFragment
    4. 中实例化
    5. "服务" '#34; application"的子组件提供所有Services所需对象的组件。在每个Service实例化。
    6. 以下是如何实施相同方法的示例。

      2017年7月编辑

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

      编辑2018年2月

      我发表了complete course about dependency injection in 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;
          }
      }
      

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

      Dagger 2 Scopes Demystified

      Dependency Injection in Android

答案 1 :(得分:14)

有关如何组织组件,模块和软件包的一些最佳示例,请参阅Google Android架构蓝图Github repo here

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

addedittask
taskdetail
tasks

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

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

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

答案 2 :(得分:3)

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

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

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

我已经同时使用了它们,它们都有效并且都有意义。

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

答案 3 :(得分:1)

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


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

答案 4 :(得分:-6)

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

通常我们会这样做

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注释。 Dagger只会在目标类中执行@Inject时创建对象。它不会在您的方案中创建其他演示者。您可以尝试使用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进入活动而不是app init时才会创建对象。