Давно работаю с dagger2. И я запутался, создавая собственный компонент / модуль для каждого Activity / Fragment. Пожалуйста, помогите мне прояснить это:
Например, у нас есть приложение, в котором около 50 экранов. Мы реализуем код по шаблону MVP и Dagger2 для DI. Предположим, у нас есть 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, ....) }
Его объяснение таково: ему не нужно создавать компоненты и модули для каждого вида деятельности. Я думаю, что идея моих друзей совершенно не хороша, но, пожалуйста, поправьте меня, если я ошибаюсь. Вот причины:
Очень много утечек памяти :
- Приложение создаст 50 докладчиков, даже если у пользователя открыто только 2 действия.
- После того, как пользователь закроет действие, его ведущий останется
Что произойдет, если я захочу создать два экземпляра одного Activity? (как он может создать двух докладчиков)
Инициализация приложения займет много времени (потому что ему нужно создать много презентаторов, объектов, ...)
Извините за длинный пост, но, пожалуйста, помогите мне прояснить это для меня и моего друга, я не могу его убедить. Будем очень признательны за ваши комментарии.
/ ------------------------------------------------- ---------------------- /
Отредактируйте после демонстрации.
Во-первых, спасибо за ответ @pandawarrior. Я должен был создать демо, прежде чем задавать этот вопрос. Я надеюсь, что мой вывод может помочь кому-то другому.
- То, что сделал мой друг, не вызывает утечки памяти, если он не помещает какую-либо область действия в методы Provides. (Например, @Singleton или @UserScope, ...)
- Мы можем создать много презентаторов, если у Provides-метода нет Scope. (Итак, мой второй пункт тоже неверен)
- Dagger будет создавать презентаторов только тогда, когда они нужны. (Итак, инициализация приложения не займет много времени, меня смутила Lazy Injection)
Итак, все причины, которые я сказал выше, в основном ошибочны. Но это не означает, что мы должны следовать идее моего друга по двум причинам:
Это плохо для архитектуры источника, когда он помещает всех презентаторов в модуль / компонент. (Это нарушает принцип разделения интерфейса , возможно, принцип единой ответственности ).
Когда мы создаем Scope Component, мы будем знать, когда он создан, а когда уничтожен, что является огромным преимуществом для предотвращения утечек памяти. Итак, для каждого действия мы должны создать компонент с @ActivityScope. Давайте представим, с реализацией моих друзей, что мы забыли добавить Scope в Provider-method => произойдут утечки памяти.
На мой взгляд, с небольшим приложением (всего несколько экранов без многих зависимостей или с аналогичными зависимостями) мы могли бы применить идею моих друзей, но, конечно, это не рекомендуется.
Предпочитаю читать дальше: Что определяет жизненный цикл компонента (графа объектов) в Dagger 2? Объем деятельности Dagger2, сколько модулей / компонентов мне нужно?
И еще одно замечание: если вы хотите увидеть, когда объект был уничтожен, вы можете вызвать методы метода вместе, и сборщик мусора запустится немедленно:
System.runFinalization();
System.gc();
Если вы используете только один из этих методов, сборщик мусора запустится позже, и вы можете получить неверные результаты.
ControllerModule
создаст новый,Presenter
а затем презентатор будет введен в файлActivity
илиFragment
. Есть твердое мнение за или против?ControllerComponent
должен их ввести. Проводите ли вы их внутриControllerModule
или вводите дополнительный модуль, зависит от вас. В реальных приложениях я советую использовать многокомпонентный подход вместо того, чтобы помещать все в один модуль. Вот примерApplicationComponent
, но контроллер будет таким же: github.com/techyourchance/idocare-android/tree/master/app/src/…ApplicationComponent
всех зависимостях, которыеControllerComponent
можно использовать. Также количество методов сгенерированного кода будет выше. Я пока не нашел веской причины использовать зависимые компоненты.dagger.android
пакета, потому что считаю это плохо мотивированным. Таким образом, этот пример все еще очень актуален и по-прежнему является лучшим способом сделать DI в Android IMHO.Некоторые из лучших примеров организации ваших компонентов, модулей и пакетов можно найти в репозитории Google Android Architecture Blueprints на Github здесь .
Если вы изучите исходный код там, вы увидите, что есть один компонент с областью действия приложения (с жизненным циклом, равным продолжительности всего приложения), а затем отдельные компоненты с областью действия для действия и фрагмента, соответствующие данной функциональности в проект. Например, есть следующие пакеты:
Внутри каждого пакета есть модуль, компонент, презентатор и т.д. Например, внутри
taskdetail
есть следующие классы:TaskDetailActivity.java TaskDetailComponent.java TaskDetailContract.java TaskDetailFragment.java TaskDetailPresenter.java TaskDetailPresenterModule.java
Преимущество такой организации (вместо группирования всех действий в один компонент или модуль) состоит в том, что вы можете воспользоваться модификаторами доступности Java и выполнить пункт 13 Эффективного Java. Другими словами, функционально сгруппированные классы будут в одном и том же package, и вы можете воспользоваться модификаторами
protected
иpackage-private
модификаторами доступности, чтобы предотвратить непреднамеренное использование ваших классов.источник
Первый вариант создает компонент с подзаданной областью для каждого действия, где действие может создавать компоненты с подобластью, которые предоставляют только зависимость (презентатора) для этого конкретного действия.
Второй вариант создает один
@Singleton
компонент, который может предоставлять презентаторов как зависимости с незаданной областью, то есть при доступе к ним вы каждый раз создаете новый экземпляр презентатора. (Нет, он не создает новый экземпляр, пока вы его не запросите).Технически ни один из подходов не хуже другого. Первый подход разделяет докладчиков не по функциям, а по уровням.
Я использовал оба, они оба работают и оба имеют смысл.
Единственный недостаток первого решения (если вы используете
@Component(dependencies={...}
вместо@Subcomponent
) заключается в том, что вам нужно убедиться, что это не Activity, который создает свой собственный модуль внутри, потому что тогда вы не можете заменить реализации метода модуля на mocks. Опять же, если вы используете инъекцию конструктора вместо инъекции поля, вы можете просто создать класс напрямую с помощью конструктора, напрямую давая ему макеты.источник
Используйте
Provider<"your component's name">
вместо простых компонентов реализацию, чтобы избежать утечек памяти и создания множества бесполезных компонентов. Поэтому ваши компоненты будут созданы ленивым методом при вызове метода get (), поскольку вместо этого вы предоставляете не экземпляр компонента, а просто поставщик. Таким образом, ваш презентатор будет применен, если был вызван .get () провайдера. Прочтите о Провайдере здесь и примените его. ( Официальная документация Dagger )И еще один отличный способ - использовать множественное связывание. В соответствии с ним вы должны привязать своих докладчиков к карте и создавать их через провайдеров, когда вам нужно. ( вот документы о мультибиндинге )
источник
Ваш друг прав, вам действительно не нужно создавать компоненты и модули для каждой деятельности. Предполагается, что Dagger поможет вам уменьшить беспорядочный код и сделать ваши действия Android более чистыми, делегируя экземпляры классов модулям вместо того, чтобы создавать их экземпляры в методе onCreate 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. } }
Вы делаете это вместо
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); }}
Так что писать слишком много вещей, чтобы победить цель кинжала, нет? Я скорее создаю своих докладчиков в Activity, если мне нужно создавать модули и компоненты для каждого Activity.
Что касается ваших вопросов о:
1- Утечка памяти:
Нет, если вы не добавите
@Singleton
аннотацию к докладчикам, которых вы предоставляете. Dagger будет создавать объект только тогда, когда вы выполняете@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'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
в действия, а не при инициализации приложения.источник