O que determina o ciclo de vida de um componente (gráfico de objeto) no Dagger 2?

134

Estou tentando entender os escopos no Dagger 2, especificamente o ciclo de vida dos gráficos com escopo. Como você cria um componente que será limpo quando você sair do escopo.

No caso de um aplicativo Android, usando o Dagger 1.x, geralmente você tem um escopo raiz no nível do aplicativo que estenderia para criar um escopo filho no nível da atividade.

public class MyActivity {

    private ObjectGraph mGraph;

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

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

O escopo filho existia desde que você mantivesse uma referência a ele, que nesse caso era o ciclo de vida da sua Atividade. A eliminação da referência no onDestroy garantiu que o gráfico com escopo estivesse livre para ser coletado como lixo.

EDITAR

Jesse Wilson publicou recentemente um mea culpa

O Dagger 1.0 estragou muito seus nomes de escopo ... A anotação @Singleton é usada para gráficos raiz e gráficos personalizados, por isso é complicado descobrir qual é o escopo real de uma coisa.

e tudo o mais que li / ouvi apontou para o Dagger 2, melhorando a maneira como os escopos funcionam, mas estou lutando para entender a diferença. De acordo com o comentário de @Kirill Boyarshinov abaixo, o ciclo de vida de um componente ou dependência ainda é determinado, como sempre, por referências concretas. Então, a diferença entre os escopos Dagger 1.xe 2.0 é apenas uma questão de clareza semântica?

Meu entendimento

Adaga 1.x

Dependências eram @Singletonou não. Isso também se aplica às dependências no gráfico raiz e nos subgráficos, levando à ambiguidade em relação a qual gráfico a dependência estava vinculada (consulte No punhal, existem singletons no subgrafo em cache ou eles sempre serão recriados quando um novo subgráfico de atividade é construído? )

Dagger 2.0

Os escopos personalizados permitem criar escopos semanticamente claros, mas são funcionalmente equivalentes à aplicação @Singletonno 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;
    }

}

O argumento é que o uso @PerActivitycomunica sua intenção com relação ao ciclo de vida desse componente, mas, em última análise, você pode usá-lo em qualquer lugar / a qualquer momento. A única promessa de Dagger é que, para um determinado componente, os métodos anotados por escopo retornem uma única instância. Suponho também que o Dagger 2 use a anotação de escopo no componente para verificar se os módulos fornecem apenas dependências que estão no mesmo escopo ou sem escopo.

Em suma

As dependências ainda são singleton ou não singleton, mas @Singletonagora são destinadas a instâncias singleton no nível do aplicativo e escopos personalizados são o método preferido para anotar dependências singleton com um ciclo de vida mais curto.

O desenvolvedor é responsável por gerenciar o ciclo de vida dos componentes / dependências eliminando referências que não são mais necessárias e responsável por garantir que os componentes sejam criados apenas uma vez no escopo a que se destinam, mas as anotações de escopo personalizadas facilitam a identificação desse escopo .

A pergunta de US $ 64 mil *

O meu entendimento dos escopos e ciclos de vida do Dagger 2 está correto?

* Na verdade, não é uma pergunta de US $ 64.000.

Enrico
fonte
5
Você não perdeu nada. O gerenciamento do ciclo de vida de cada componente é manual. Da minha própria experiência, o mesmo ocorreu no Dagger 1 também. Ao subgráficos do objeto ObjectGraph no nível do aplicativo, usando a plus()referência a novo gráfico, foi armazenado em Activity e vinculado ao seu ciclo de vida (sem referência em onDestroy). Quanto aos escopos, eles garantem que suas implementações de componentes sejam geradas sem erros no tempo de compilação, com todas as dependências satisfeitas. Portanto, não apenas para fins de documentação. Confira alguns exemplos deste tópico .
Kirill Boyarshinov
1
Só para esclarecer isso, métodos de provedor "sem escopo" retornam novas instâncias a cada injeção?
user1923613
2
Por que você define component = null; em onDestroy ()?
Marian Paździoch

Respostas:

70

Quanto à sua pergunta

O que determina o ciclo de vida de um componente (gráfico de objeto) no Dagger 2?

A resposta curta é você determiná-lo . Seus componentes podem receber um escopo, como

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

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

Estes são úteis para você por duas coisas:

  • Validação do escopo: um componente pode ter apenas provedores sem escopo ou provedores com escopo no mesmo escopo que seu componente.

.

@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();
    }
}
  • Permite sub-escopo suas dependências com escopo, permitindo assim que você crie um componente "sub-escopo" que usa as instâncias fornecidas a partir do componente "super-escopo".

Isso pode ser feito com @Subcomponentanotações ou dependências de componentes. Eu pessoalmente prefiro dependências.

@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));

Ou você pode usar dependências de componentes como essas

@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();

Coisas importantes a saber:

  • Um provedor com escopo definido cria uma instância para esse escopo especificado para cada componente . Significa que um componente controla suas próprias instâncias, mas outros componentes não têm um conjunto de escopos compartilhados ou alguma mágica. Para ter uma instância em um determinado escopo, você precisa de uma instância do componente. É por isso que você deve fornecer ApplicationComponentpara acessar suas próprias dependências com escopo.

  • Um componente pode sub-escopo apenas um componente com escopo. Várias dependências de componente com escopo definido não são permitidas.

EpicPandaForce
fonte
Um componente pode sub-escopo apenas um componente com escopo. Múltiplas dependências de componentes com escopo não são permitidas (nem mesmo se todas tiverem escopos diferentes, embora eu ache que isso seja um bug). realmente não entendo o que isso significa
Damon Yuan
Mas e o ciclo de vida? O ActivityComponent será candidato a coletor de lixo se a atividade for destruída?
Sever
Se você não armazená-lo em outro lugar, então sim #
EpicPandaForce 21/17/17
1
Portanto, se precisarmos de componentes e objetos injetados ao longo da Activity, construímos componentes dentro da Activity. Se desejamos apenas sobreviver através de um fragmento, devo construir um componente dentro do fragmento, certo? Onde você mantém a instância do componente faz escopo?
Trácia
O que devo fazer se desejar sobreviver a uma atividade específica?
Thracian