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

Mr *_*ike 79 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, ....)
    
    }
    
    Run Code Online (Sandbox Code Playgroud)
  2. 创建ActivityScope:

    @Scope
    @Documented
    @Retention(value=RUNTIME)
    public @interface ActivityScope {
    }
    
    Run Code Online (Sandbox Code Playgroud)
  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.
    
    Run Code Online (Sandbox Code Playgroud)

这些只是非常简单的例子来说明我将如何实现它.

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

  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.
    
    }
    
    Run Code Online (Sandbox Code Playgroud)
  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, ....)
    
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

  1. 很多内存泄漏:

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

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

很抱歉很长的帖子,但请帮我澄清这个对我和我的朋友,我无法说服他. 您的意见将非常感谢.

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

进行演示后编辑.

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

  1. 我的朋友所做的事情不会导致内存泄漏,除非他将任何Scope放入Provide-methods.(例如@Singleton,或@UserScope,......)
  2. 如果Provide-method没有任何Scope,我们可以创建许多演示者.(所以,我的第二点也错了)
  3. 只有在需要时,Dagger才会创建演示者.(所以,应用程序不会花很长时间进行初始化,我对Lazy Injection感到困惑)

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

  1. 当他进入模块/组件中的所有演示者时,它对于源的体系结构并不好.(它违反了接口隔离原则,也许是单一责任原则).

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

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

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

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

    System.runFinalization();
    System.gc();
Run Code Online (Sandbox Code Playgroud)

如果您只使用其中一种方法,GC将在稍后运行,您可能会得到错误的结果.

Vas*_*liy 77

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

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

  1. 它将您限制为只有一个范围(@Singleton或自定义范围)
  2. 您被限制的唯一范围使注入的对象成为"应用程序单例",因此范围内的错误或作用域对象的错误使用很容易导致全局内存泄漏
  3. 您也希望使用Dagger2进行注入Services,但Services可能需要不同的对象Activities(例如Services,不需要演示者,不需要FragmentManager等等).通过使用单个组件,您可以放松为不同组件定义不同对象图的灵活性.

因此,每个组件Activity都是过度杀伤,但整个应用程序的单个组件不够灵活.最佳解决方案介于这些极端之间(通常是这样).

我使用以下方法:

  1. 单个"应用程序"组件,提供"全局"对象(例如,保存全局状态的对象,在应用程序中的所有组件之间共享).实例化Application.
  2. "应用程序"组件的"控制器"子组件,提供所有面向用户的"控制器"所​​需的对象(在我的架构中这些是ActivitiesFragments).在每个Activity和实例中实例化Fragment.
  3. "应用程序"组件的"服务"子组件,提供所有人都需要的对象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);
    }
}
Run Code Online (Sandbox Code Playgroud)

控制器范围:

@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)
}
Run Code Online (Sandbox Code Playgroud)

然后在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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

匕首2范围揭秘

Android中的依赖注入

  • 感谢@vasiliy 分享您的意见。这正是我使用它的方式以及当前遵循的策略。在 MVP 模式的情况下,引用的“ControllerModule”将创建一个新的“Presenter”,然后将 Presenter 注入到“Activity”或“Fragment”中。有支持或反对的明确意见吗? (2认同)
  • @ Mr.Hyde,总的来说是的,但是你必须在`ApplicationComponent`中明确声明`ControllerComponent`可以使用的所有依赖项.此外,生成的代码的方法计数也会更高.我还没有找到使用依赖组件的充分理由. (2认同)

Dav*_*son 14

一些如何组织你的组件,模块,和包最好的例子可以在谷歌Android架构蓝图GitHub库中找到这里.

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

addedittask
taskdetail
tasks
Run Code Online (Sandbox Code Playgroud)

每个包内都有一个模块,组件,演示器等.例如,里面taskdetail有以下类:

TaskDetailActivity.java
TaskDetailComponent.java
TaskDetailContract.java
TaskDetailFragment.java
TaskDetailPresenter.java
TaskDetailPresenterModule.java
Run Code Online (Sandbox Code Playgroud)

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

  • 这也是我的首选方法。我不喜欢活动/片段访问它们不应该访问的东西。 (2认同)

Lie*_*ung -6

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

通常我们会这样做

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.
}

}
Run Code Online (Sandbox Code Playgroud)

你这样做

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);
}}
Run Code Online (Sandbox Code Playgroud)

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

至于您的问题:

1-内存泄漏:

不,除非您@Singleton为您提供的演示者添加注释。@InjectDagger 仅在您在目标类中执行操作时才会创建对象。它不会在您的场景中创建其他演示者。您可以尝试使用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.
Run Code Online (Sandbox Code Playgroud)

}

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
Run Code Online (Sandbox Code Playgroud)

@Inject3. 不,只有当您进入活动时才会创建对象,而不是应用程序初始化时创建。