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

我一直在用匕首工作一段时间。 而且我也困惑于为每个Activity / Fragment创build一个自己的组件/模块。 请帮我澄清一下:

例如,我们有一个应用程序,该应用程序有大约50个屏幕。 我们将按照MVP模式和DI的Dagger2执行代码。 假设我们有50个活动和50个主持人。

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

  1. 创build一个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. 创buildActivityScope:

     @Scope @Documented @Retention(value=RUNTIME) public @interface ActivityScope { } 
  3. 为每个活动创build组件和模块。 通常我会把它们放在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. 创buildPresenterModule,它将提供所有演示者:

     @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. 创buildAppModule和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, ....) } 

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

  1. 很多内存泄漏

    • 即使用户只打开了2个活动,应用程序也会创build50个演示者。
    • 用户closures活动后,其演示者仍将保留
  2. 如果我想创build一个Activity的两个实例,会发生什么? (他怎么能创造两个主持人)

  3. 初始化应用程序需要很长时间(因为它必须创build许多演示者,对象…)

对不起,很长的文章,但请帮我澄清这一点,我和我的朋友,我无法说服他。 您的意见将非常感激。

/ ————————————————- ———————- /

做一个演示后编辑。

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

  1. 我的朋友所做的不会导致内存泄漏,除非他将任何范围放到提供方法中。 (例如@Singleton或@UserScope,…)
  2. 如果提供方法没有任何范围,我们可以创build许多演示者。 (所以,我的第二点也是错误的)
  3. 匕首只有在需要时才会创build演示者。 (所以,应用程序不需要很长时间来初始化,我被懒惰注入混淆)

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

  1. 当他进入模块/组件的所有演示者时,对于源代码的架构并不好。 (它违反界面隔离原则 ,也可能是单一责任原则)。

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

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

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

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

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

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

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

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

  1. 它限制你只有一个范围( @Singleton或自定义)
  2. 你被限制的唯一的作用域使得注入的对象是“应用程序单例”,因此作用域对象的作用域或错误使用的错误很容易导致全局内存泄漏
  3. 你也想使用Dagger2来注入Services ,但是Services可能需要不同的对象而不是Activities (比如Services不需要演示者,没有FragmentManager等等)。 通过使用单个组件,您可以灵活地为不同的组件定义不同的对象图。

所以,每个Activity一个组件是一个矫枉过正的问题,但是整个应用程序的单个组件并不够灵活。 最佳的解决scheme是在这两个极端之间(通常是这样)。

我使用以下方法:

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

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


编辑2017年7月

我发布了一个video教程,演示如何在Android应用程序中构buildDaggerdependency injection代码: Android Dagger for Professionals教程 。


应用范围:

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

有关dependency injection的其他信息:

匕首2范围揭秘

Android中的dependency injection

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

第二个选项创build一个单独的@Singleton组件,它能够将演示者作为无标度的依赖关系来提供,这意味着当你访问他们时,你每次都创build一个演示者的新实例。 (不,它没有创build一个新的实例,直到你请求一个)。


从技术上讲,两种方法都不如另一种方法。 第一种方法不是按function分开主持人,而是按层分开。

我使用了两个,他们都工作,都有道理。

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

关于如何组织你的组件,模块和包的一些最好的例子可以在Google Android Architecture Blueprints Github repo中find 。

如果您在那里检查源代码,您可以看到有一个应用程序范围的组件(具有整个应用程序持续时间的生命周期),然后单独的活动范围组件与活动和片段相对应项目。 例如,有以下软件包:

 addedittask taskdetail tasks 

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

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

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

你的朋友是正确的,你不必为每个活动创build组件和模块。 Dagger应该帮助你减less混乱的代码,并通过将类实例委派给模块来使你的Android活动变得更清洁,而不是在Activities的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); }} 

那么写太多的东西有种打败匕首的目的不行? 我宁愿在活动中实例化我的演示者,如果我必须为每个活动创build模块和组件。

至于你的问题:

1-内存泄漏:

不,除非你给你提供的演示者添加@Singleton注释。 匕首只会在目标类中执行@Inject时创build对象。 它不会在您的场景中创build其他演示者。 您可以尝试使用日志来查看它们是否被创build。

 @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-注入两次并logging他们的散列码

 //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活动时才会创build,而不是app init。