Что определяет жизненный цикл компонента (графа объектов) в кинжале 2?

Я пытаюсь обернуть голову вокруг областей в 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;
    }
}

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

ИЗМЕНИТЬ

Недавно Джесси Уилсон опубликовал mea culpa

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

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

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

Кинжал 1.x

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

Кинжал 2.0

Пользовательские области позволяют создавать семантически прозрачные области, но функционально эквивалентны применению @Singleton в кинжале 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;
    }

}

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

В резюме

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

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

Вопрос $64k *

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

* На самом деле вопрос не стоит $64'000.

Ответ 1

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

Что определяет жизненный цикл компонента (графа объектов) в кинжале 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 для доступа к своим зависимым зависимостям.

  • Компонент может подкачать только один компонент с областью.

    Не допускается использование зависимостей компонентов с несколькими областями.

Ответ 2

Как насчет жизненного цикла созданных экземпляров? Как долго аннотированные экземпляры ActivityScope живут после того, как я уничтожу ActivityScope? Должен ли я уничтожить это самостоятельно или это делает кинжал?

Краткий ответ: вы сами это определяете.

Но как? Означает ли название MainActivityScope/MainActivity все волшебство?

Пока я нахожу здесь только один приемлемый ответ: fooobar.com/info/231045/...

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

Итак, аннотации - это просто тэг Singleton, что действительно важно для жизненного цикла ваших зависимостей - где вы создаете компонент