От чего зависит жизненный цикл компонента (графа объектов) в Dagger 2?

134

Я пытаюсь осмыслить области видимости в Dagger 2, в частности, жизненный цикл графиков с областью видимости. Как создать компонент, который будет очищен при выходе из области видимости.

В случае приложения Android при использовании Dagger 1.x у вас обычно есть корневая область видимости на уровне приложения, которую вы расширяете для создания дочерней области на уровне активности.

public class MyActivity {

    private ObjectGraph mGraph;

    public void onCreate() {
        mGraph = ((MyApp) getApplicationContext())
            .getObjectGraph()
            .plus(new ActivityModule())
            .inject(this);
    }

    public void onDestroy() {
        mGraph = null;
    }
}

Дочерняя область существует до тех пор, пока вы сохраняете на нее ссылку, которая в данном случае была жизненным циклом вашей Activity. Удаление ссылки в onDestroy гарантирует, что граф с областью видимости свободен для сборки мусора.

РЕДАКТИРОВАТЬ

Джесси Уилсон недавно опубликовал мою ошибку

Dagger 1.0 сильно напортачил с именами своих областей видимости ... Аннотация @Singleton используется как для корневых графов, так и для пользовательских графов, поэтому сложно определить, какова реальная область действия объекта.

и все остальное, что я читал / слышал, указывает на то, что Dagger 2 улучшает работу прицелов, но я изо всех сил пытаюсь понять разницу. Согласно комментарию @Kirill Boyarshinov ниже, жизненный цикл компонента или зависимости по-прежнему определяется, как обычно, конкретными ссылками. Так является ли разница между прицелами Dagger 1.x и 2.0 исключительно вопросом семантической ясности?

Мое понимание

Кинжал 1.x

Зависимости были @Singletonили нет. Это в равной степени относится к зависимостям в корневом графе и подграфах, что приводит к неоднозначности относительно того, к какому графу была привязана зависимость (см. In Dagger - это синглтоны внутри подграфа, кэшируемые или они всегда будут воссозданы при создании нового подграфа активности построен? )

Кинжал 2.0

Пользовательские области видимости позволяют создавать семантически чистые области действия, но функционально эквивалентны применению @Singletonв Dagger 1.x.

// Application level
@Singleton
@Component( modules = MyAppModule.class )
public interface MyAppComponent {
    void inject(Application app);
}

@Module
public class MyAppModule {

    @Singleton @Named("SingletonScope") @Provides
    StringBuilder provideStringBuilderSingletonScope() {
        return new StringBuilder("App");
    }
}

// Our custom scope
@Scope public @interface PerActivity {}

// Activity level
@PerActivty
@Component(
    dependencies = MyAppComponent.class,
    modules = MyActivityModule.class
)
public interface MyActivityComponent {
    void inject(Activity activity);
}

@Module
public class MyActivityModule {

    @PerActivity @Named("ActivityScope") @Provides
    StringBuilder provideStringBuilderActivityScope() {
        return new StringBuilder("Activity");
    }

    @Name("Unscoped") @Provides
    StringBuilder provideStringBuilderUnscoped() {
        return new StringBuilder("Unscoped");
    }
}

// Finally, a sample Activity which gets injected
public class MyActivity {

    private MyActivityComponent component;

    @Inject @Named("AppScope")
    StringBuilder appScope

    @Inject @Named("ActivityScope")
    StringBuilder activityScope1

    @Inject @Named("ActivityScope")
    StringBuilder activityScope2

    @Inject @Named("Unscoped")
    StringBuilder unscoped1

    @Inject @Named("Unscoped")
    StringBuilder unscoped2

    public void onCreate() {
        component = Dagger_MyActivityComponent.builder()
            .myApplicationComponent(App.getComponent())
            .build()
            .inject(this);

        appScope.append(" > Activity")
        appScope.build() // output matches "App (> Activity)+" 

        activityScope1.append("123")
        activityScope1.build() // output: "Activity123"

        activityScope2.append("456")
        activityScope1.build() // output: "Activity123456"

        unscoped1.append("123")
        unscoped1.build() // output: "Unscoped123"

        unscoped2.append("456")
        unscoped2.build() // output: "Unscoped456"

    }

    public void onDestroy() {
        component = null;
    }

}

Вывод заключается в том, что using @PerActivityсообщает о вашем намерении относительно жизненного цикла этого компонента, но в конечном итоге вы можете использовать компонент где угодно / в любое время. Единственное обещание Dagger состоит в том, что для данного компонента аннотированные методы области видимости вернут единственный экземпляр. Я также предполагаю, что Dagger 2 использует аннотацию области для компонента, чтобы убедиться, что модули предоставляют только зависимости, которые либо находятся в той же области, либо не имеют области.

В итоге

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

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

Вопрос на $ 64 000 *

Правильно ли я понимаю объемы и жизненные циклы Dagger 2?

* На самом деле это не вопрос на 64 тысячи долларов.

Энрико
источник
5
Вы ничего не упустили. Управление жизненным циклом каждого компонента осуществляется вручную. По моему собственному опыту, то же самое было и в Dagger 1. При подграфе уровня приложения объект ObjectGraph с использованием plus()ссылки на новый граф сохранялся в Activity и был привязан к его жизненному циклу (разыменован в onDestroy). Что касается областей видимости, они гарантируют, что реализации ваших компонентов будут сгенерированы без ошибок во время компиляции, и все зависимости будут удовлетворены. Так что это не только для документации. Посмотрите несколько примеров из этой ветки .
Кирилл Бояршинов
1
Чтобы прояснить это, "незаданные" методы провайдера возвращают новые экземпляры при каждой инъекции?
user1923613
2
Почему вы устанавливаете component = null; в onDestroy ()?
Marian Padzioch

Ответы:

70

Что касается вашего вопроса

От чего зависит жизненный цикл компонента (графа объектов) в Dagger 2?

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

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ApplicationScope {
}

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {
}

Они полезны для вас по двум причинам:

  • Проверка области действия: компонент может иметь только поставщиков с незаданной областью или поставщиков той же области, что и ваш компонент.

,

@Component(modules={ApplicationModule.class})
@ApplicationScope
public interface ApplicationComponent {
    Something something();
    AnotherThing anotherThing();

    void inject(Whatever whatever);
}

@Module
public class ApplicationModule {
    @ApplicationScope //application-scoped provider, only one can exist per component
    @Provides
    public Something something() {
         return new Something();
    }

    @Provides //unscoped, each INJECT call creates a new instance
    public AnotherThing anotherThing() {
        return new AnotherThing();
    }
}
  • Позволяет разделить область видимости на зависимости, что позволит вам создать компонент с дополнительной областью, который использует предоставленные экземпляры из компонента с расширенной областью действия.

Это можно сделать с помощью @Subcomponentаннотаций или зависимостей компонентов. Я лично предпочитаю зависимости.

@Component(modules={ApplicationModule.class})
@ApplicationScope
public interface ApplicationComponent {
    Something something();
    AnotherThing anotherThing();

    void inject(Whatever whatever);

    ActivityComponent newActivityComponent(ActivityModule activityModule); //subcomponent factory method
}

@Subcomponent(modules={ActivityModule.class})
@ActivityScope
public interface ActivityComponent {
    ThirdThingy thirdThingy();

    void inject(SomeActivity someActivity);
}

@Module
public class ActivityModule {
    private Activity activity;

    public ActivityModule(Activity activity) {
        this.activity = activity;
    }

    //...
}

ApplicationComponent applicationComponent = DaggerApplicationComponent.create();
ActivityComponent activityComponent = applicationComponent.newActivityComponent(new ActivityModule(SomeActivity.this));

Или вы можете использовать зависимости компонентов, например

@Component(modules={ApplicationModule.class})
@ApplicationScope
public class ApplicationComponent {
    Something something(); 
    AnotherThing anotherThing();

    void inject(Whatever whatever);
}

@Component(dependencies={ApplicationComponent.class}, modules={ActivityModule.class})
@ActivityScope
public interface ActivityComponent extends ApplicationComponent {
    ThirdThingy thirdThingy();

    void inject(SomeActivity someActivity);
}

@Module
public class ActivityModule {
    private Activity activity;

    public ActivityModule(Activity activity) {
        this.activity = activity;
    }

    //...
}

ApplicationComponent applicationComponent = DaggerApplicationComponent.create();
ActivityComponent activityComponent = DaggerActivityComponent.builder().activityModule(new ActivityModule(SomeActivity.this)).build();

Важные вещи, которые нужно знать:

  • Поставщик с заданной областью действия создает по одному экземпляру для данной области для каждого компонента . Это означает, что компонент отслеживает свои собственные экземпляры, но у других компонентов нет общего пула областей видимости или какой-то магии. Чтобы иметь один экземпляр в данной области, вам нужен один экземпляр компонента. Вот почему вы должны предоставить ApplicationComponentобъекту для доступа к его собственным зависимостям в области видимости.

  • Компонент может входить только в один компонент с заданной областью действия. Зависимости компонентов с несколькими областями видимости не допускаются.

EpicPandaForce
источник
Компонент может включать только один компонент с заданной областью действия. Зависимости компонентов с несколькими областями видимости не допускаются (даже если все они имеют разные области действия, хотя я думаю, что это ошибка). не совсем понимаю, что это значит
Дэймон Юань
Но как насчет livecycle. Будет ли ActivityComponent кандидатом на роль сборщика мусора в случае уничтожения активности?
Sever
Если вы не храните его где-нибудь еще, тогда да
EpicPandaForce
1
Поэтому, если нам нужен компонент и внедренный объект, работающий через Activity, мы создаем компонент внутри Activity. Если мы хотим выжить только через фрагмент, я должен создать компонент внутри фрагмента, верно? Где вы храните экземпляр компонента?
Thracian
Что мне делать, если я хочу, чтобы он выжил благодаря определенной деятельности?
Thracian