Подтвердить что ты не робот

Кинжал. Должны ли мы создавать каждый компонент и модуль для каждого Activity/Fragment

Я работал с dagger2 некоторое время. И я запутался, создавая собственный компонент/модуль для каждого занятия/фрагмента. Пожалуйста, помогите мне уточнить это:

Например, у нас есть приложение, а в приложении около 50 экранов. Мы реализуем код в соответствии с шаблоном MVP и Dagger2 для 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 действия.
    • После того, как пользователь закроет действие, его докладчик останется
  2. Что произойдет, если я захочу создать два экземпляра одного занятия? (как он может создать двух докладчиков)

  3. Инициализация приложения займет много времени (потому что оно должно создавать много презентаторов, объектов,...)

Извините за длинный пост, но, пожалуйста, помогите мне уточнить это для меня и моего друга, я не могу убедить его. Ваши комментарии будут очень благодарны.

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

Редактировать после выполнения демо.

Во-первых, спасибо за ответ @pandawarrior. Я должен был создать демо, прежде чем я задал этот вопрос. Я надеюсь, что мой вывод здесь может помочь кому-то еще.

  1. То, что сделал мой друг, не вызовет утечек памяти, если он не установит какую-либо область видимости для методов Provides. (Например, @Singleton или @UserScope,...)
  2. Мы можем создать много докладчиков, если у метода Provides нет области действия. (Итак, мой второй пункт тоже неверен)
  3. Кинжал будет создавать докладчиков только тогда, когда они необходимы. (Итак, приложение не займет много времени для инициализации, я был смущен Lazy Injection)

Итак, все причины, о которых я говорил выше, в основном неверны. Но это не значит, что мы должны следовать идее моего друга по двум причинам:

  1. Это не хорошо для исходной архитектуры, когда он помещает всех докладчиков в модуль/компонент. (Это нарушает принцип сегрегации интерфейса, возможно, принцип единой ответственности).

  2. Когда мы создаем компонент Scope, мы узнаем, когда он был создан, а когда уничтожен, что является огромным преимуществом для предотвращения утечек памяти. Таким образом, для каждого действия мы должны создать компонент с @ActivityScope. Давайте представим, с реализацией моих друзей, что мы забыли поместить Scope в Provider-method => утечки памяти.

По моему мнению, с небольшим приложением (всего несколько экранов без многих зависимостей или с аналогичными зависимостями) мы могли бы применить идею моих друзей, но, конечно, это не рекомендуется.

Предпочитаете читать дальше: Что определяет жизненный цикл компонента (граф объектов) в Dagger 2? Сфера деятельности Dagger2, сколько модулей/компонентов мне нужно?

И еще одно замечание: если вы хотите увидеть, когда объект уничтожен, вы можете вызвать те из методов вместе, и сборщик мусора немедленно запустится:

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

Если вы используете только один из этих методов, GC запустится позже, и вы можете получить неправильные результаты.

4b9b3361

Ответ 1

Объявление отдельного модуля для каждого Activity вообще не очень хорошая идея. Объявление отдельного компонента для каждого Activity еще хуже. Причина этого очень проста - вам не нужны все эти модули/компоненты (как вы уже видели сами).

Однако, имея только один компонент, который привязан к Application жизненного цикла и использовать его для инъекций во все Activities также не является оптимальным решением (это ваш друг подход). Это не оптимально, потому что:

  1. Он ограничивает вас только одной областью (@Singleton или пользовательская)
  2. Единственная область действия, которой вы ограничены, делает внедренные объекты "одиночными приложениями", поэтому ошибки в области видимости или неправильное использование объектов области могут легко вызвать глобальные утечки памяти
  3. Вы хотите использовать Dagger2, чтобы впрыснуть в Services тоже, но Services могут требовать различных объектов, чем Activities (например, Services не нужны докладчикам, не FragmentManager и т.д.). Используя один компонент, вы теряете гибкость определения различных графов объектов для разных компонентов.

Таким образом, компонент для каждого Activity является излишним, но один компонент для всего приложения недостаточно гибок. Оптимальное решение находится между этими крайностями (как это обычно бывает).

Я использую следующий подход:

  1. Один компонент "приложение", который предоставляет "глобальные" объекты (например, объекты, которые содержат глобальное состояние, которое совместно используется всеми компонентами в приложении). Установлено в Application.
  2. Подкомпонент "Контроллер" компонента "приложение", который предоставляет объекты, необходимые для всех "контроллеров", обращающихся к пользователю (в моей архитектуре это Activities и Fragments). Установлено в каждой Activity и Fragment.
  3. Подкомпонент "Сервис" компонента "приложение", который предоставляет объекты, необходимые для всех Services. Установлен в каждой Service.

Ниже приведен пример того, как вы могли бы реализовать тот же подход.


Редактировать июль 2017

Я опубликовал видеоурок, в котором показано, как структурировать код внедрения зависимостей Dagger в приложении Android: Android Dagger for Professionals Tutorial.


Редактировать февраль 2018

Я опубликовал полный курс о внедрении зависимостей в Android.

В этом курсе я объясню теорию внедрения зависимостей и покажу, как она естественным образом возникает в приложениях для Android. Затем я продемонстрирую, как конструкции Даггера вписываются в общую схему внедрения зависимостей.

Если вы пройдете этот курс, вы поймете, почему идея иметь отдельное определение модуля/компонента для каждого занятия/фрагмента в основном ошибочна в наиболее фундаментальном смысле.

Такой подход приводит к тому, что структура уровня представления из "функционального" набора классов зеркально отражается в структуре "конструкторского" набора классов, таким образом связывая их вместе. Это идет вразрез с основной целью внедрения зависимостей, которая заключается в том, чтобы разделить наборы классов "Конструкция" и "Функциональный".


Область применения:

@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

Ответ 2

Некоторые из лучших примеров того, как организовать свои компоненты, модули и пакеты могут быть найдены в репо Google Android Архитектура светокопий Github здесь.

Если вы изучите исходный код там, то увидите, что есть один единственный компонент в области приложения (с жизненным циклом продолжительности всего приложения), а затем отдельные компоненты в области действия для действия и фрагмента, соответствующие данной функциональности в проект. Например, есть следующие пакеты:

addedittask
taskdetail
tasks

Внутри каждого пакета есть модуль, компонент, презентатор и т.д. Например, внутри taskdetail есть следующие классы:

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

Преимущество организации этого способа (а не группирования всех действий в одном компоненте или модуле) состоит в том, что вы можете воспользоваться преимуществами модификаторов доступности Java и выполнить элемент 13 действующей Java. Другими словами, функционально сгруппированные классы будут в одном и том же пакет, и вы можете использовать protected и package-private для package-private модификаторы доступности, чтобы предотвратить непреднамеренное использование ваших классов.

Ответ 3

Первая опция создает подкопированный компонент для каждого действия, где активность может создавать подкопные компоненты, которые предоставляют только зависимую (ведущую) для этого конкретного действия.

Вторая опция создает единственный компонент @Singleton, который может предоставить докладчиков как незащищенные зависимости, а это означает, что при обращении к ним вы каждый раз создаете новый экземпляр ведущего. (Нет, он не создает новый экземпляр, пока вы не запросите его).


Технически ни один подход не хуже другого. Первый подход не разделяет презентаторов по признаку, а по слою.

Я использовал оба, оба они работают, и оба имеют смысл.

Единственным недостатком первого решения (если вы используете @Component(dependencies={...} вместо @Subcomponent) является то, что вам нужно убедиться, что это не Activity, который создает собственный модуль внутри, потому что тогда вы не можете заменить метод модуля реализаций с издевательствами. Опять же, если вы используете инъекцию конструктора вместо инъекции поля, вы можете просто создать класс непосредственно с помощью конструктора, напрямую давая ему mocks.

Ответ 4

Ваш друг прав, вам действительно не нужно создавать компоненты и модули для всех видов деятельности. Кинжал должен помочь вам уменьшить беспорядочный код и сделать ваши действия в 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 к презентаторам, которые вы предоставляете. Кинжал будет создавать объект только тогда, когда вы выполняете @Inject в целевом классе. Он не будет создавать других докладчиков в вашем сценарии. Вы можете попробовать использовать журнал, чтобы узнать, созданы ли они или нет.

@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 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 в действиях, а не в приложении init.