我已经和dagger2合作了一段时间。我为每个Activity / Fragment创建一个自己的组件/模块感到困惑。请帮我澄清一下:
例如,我们有一个应用程序,该应用程序有大约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, ....)
}
创建ActivityScope:
@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, ....)
}
他的解释是:他不必为每项活动创建组件和模块。 我认为我的朋友的想法绝对不是很好,但如果我错了,请纠正我。原因如下:
大量内存泄漏:
如果我想创建一个Activity的两个实例,会发生什么? (他怎么能创建两个演示者)
应用程序初始化需要花费大量时间(因为它必须创建许多演示者,对象......)
很抱歉很长的帖子,但请帮助我为我和我的朋友澄清一下,我无法说服他。 非常感谢您的评论。
/ ------------------------------------------- ---------------------------- /
进行演示后编辑。
首先,感谢@pandawarrior回答。 在我提出这个问题之前,我应该创建一个Demo。我希望我的结论可以帮助别人。
所以,我上面提到的所有原因大多都是错误的。但这并不意味着我们应该遵循我的朋友的想法,原因有两个:
当他进入模块/组件中的所有演示者时,它对源的体系结构不利。 (它也违反Interface segregation principle,也许Single Responsibility原则。
当我们创建一个Scope Component时,我们将知道它何时被创建以及它何时被销毁,这对于避免内存泄漏是一个巨大的好处。因此,对于每个Activity,我们应该使用@ActivityScope创建一个Component。让我们想象一下,在朋友实施的情况下,我们忘记在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将在稍后运行,您可能会得到错误的结果。
答案 0 :(得分:70)
为每个Activity
声明一个单独的模块根本不是一个好主意。为每个Activity
声明单独的组件更糟糕。这背后的原因非常简单 - 您并不需要所有这些模块/组件(正如您自己已经看到的那样)。
但是,只有一个与Application
生命周期相关联的组件并将其用于注入所有Activities
的组件也不是最佳解决方案(这是您的朋友&# 39; s方法)。这不是最佳的,因为:
@Singleton
或自定义范围)Services
,但Services
可能需要与Activities
不同的对象(例如Services
don&#39 ; t需要演示者,不要FragmentManager
等。通过使用单个组件,您可以灵活地为不同的组件定义不同的对象图。因此,每个Activity
的组件是一种过度杀伤,但整个应用程序的单个组件不够灵活。最佳解决方案介于这两个极端之间(通常是这样)。
我使用以下方法:
Application
。Activities
和Fragments
)。在每个Activity
和Fragment
。Services
所需对象的组件。在每个Service
实例化。以下是如何实施相同方法的示例。
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;
}
}
有关依赖注入的其他信息:
答案 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.换句话说,功能分组的类将是在同一个程序包中,您可以利用protected
和package-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时才会创建对象。