Cosa determina il ciclo di vita di un componente (oggetto grafico) in Dagger 2?


134

Sto cercando di avvolgere la mia testa attorno agli ambiti in Dagger 2, in particolare il ciclo di vita dei grafici con ambito. Come si crea un componente che verrà ripulito quando si esce dall'ambito.

Nel caso di un'applicazione Android, usando Dagger 1.x si ha generalmente un ambito radice a livello di applicazione che si estenderebbe per creare un ambito figlio a livello di attività.

public class MyActivity {

    private ObjectGraph mGraph;

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

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

L'ambito figlio esisteva fintanto che hai mantenuto un riferimento ad esso, che in questo caso era il ciclo di vita della tua attività. Eliminare il riferimento in onDestroy ha assicurato che il grafico con ambito fosse libero di essere raccolto.

MODIFICARE

Jesse Wilson ha recentemente pubblicato un mea culpa

Dagger 1.0 ha rovinato gravemente i nomi dei suoi scopi ... L'annotazione @Singleton è usata sia per i grafici di root che per quelli personalizzati, quindi è difficile capire quale sia l'ambito reale di una cosa.

e tutto il resto che ho letto / sentito punti su Dagger 2 che migliora il modo in cui funzionano gli oscilloscopi, ma faccio fatica a capire la differenza. Secondo il commento di @Kirill Boyarshinov di seguito, il ciclo di vita di un componente o di una dipendenza è ancora determinato, come al solito, da riferimenti concreti. Quindi la differenza tra gli ambiti Dagger 1.xe 2.0 è puramente una questione di chiarezza semantica?

La mia comprensione

Pugnale 1.x

Le dipendenze erano @Singletono no. Ciò era altrettanto vero per le dipendenze nel grafico principale e nei sottografi, il che porta all'ambiguità su quale grafico era legato alla dipendenza (vedi Dagger sono i Singleton all'interno del sotto-grafico memorizzati nella cache o saranno sempre ricreati quando un nuovo sotto-grafico di attività è costruito? )

Pugnale 2.0

Gli ambiti personalizzati consentono di creare ambiti semanticamente chiari, ma sono funzionalmente equivalenti all'applicazione @Singletonin 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;
    }

}

L'asporto è che l'uso @PerActivitycomunica la tua intenzione riguardo al ciclo di vita di questo componente, ma alla fine puoi utilizzare il componente ovunque / in qualsiasi momento. L'unica promessa di Dagger è che, per un dato componente, i metodi annotati dell'ambito restituiranno una singola istanza. Suppongo anche che Dagger 2 utilizzi l'annotazione dell'ambito sul componente per verificare che i moduli forniscano solo dipendenze che sono nello stesso ambito o non nell'ambito.

In sintesi

Le dipendenze sono ancora singole o non singole, ma @Singleton ora sono intese per istanze singleton a livello di applicazione e gli ambiti personalizzati sono il metodo preferito per annotare le dipendenze singleton con un ciclo di vita più breve.

Lo sviluppatore è responsabile della gestione del ciclo di vita dei componenti / dipendenze eliminando i riferimenti non più necessari e la responsabilità di garantire che i componenti vengano creati una sola volta nell'ambito nell'ambito a cui sono destinati, ma le annotazioni dell'ambito personalizzato semplificano l'identificazione di tale ambito .

La domanda da $ 64k *

La mia comprensione degli ambiti e dei cicli di vita di Dagger 2 è corretta?

* In realtà non è una domanda da $ 64'000.


5
Non ti sei perso niente. La gestione del ciclo di vita di ciascun componente è manuale. Per mia stessa esperienza, lo stesso è avvenuto nel Dagger 1. Durante la sottoregrafia dell'oggetto ObjectGraph a livello di applicazione utilizzando il plus()riferimento a un nuovo grafico è stato memorizzato in Activity ed è stato associato al suo ciclo di vita (senza riferimenti onDestroy). Per quanto riguarda gli ambiti, assicurano che le implementazioni dei componenti vengano generate senza errori in fase di compilazione, con ogni dipendenza soddisfatta. Quindi non solo a scopo di documentazione. Guarda alcuni esempi da questa discussione .
Kirill Boyarshinov il

1
Giusto per essere chiari su questo, i metodi del provider "senza ambito" restituiscono nuove istanze ad ogni iniezione?
user1923613

2
Perché imposti component = null; in onDestroy ()?
Marian Paździoch,

Risposte:


70

Per quanto riguarda la tua domanda

Cosa determina il ciclo di vita di un componente (oggetto grafico) in Dagger 2?

La risposta breve è tu la determini . Ai componenti può essere assegnato un ambito, ad esempio

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

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

Questi sono utili per te per due cose:

  • Convalida dell'ambito: un componente può avere solo provider senza ambito o provider con ambito dello stesso ambito del 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();
    }
}
  • Consente di sotto-scopare le dipendenze con ambito, consentendo così di creare un componente "subscoped" che utilizza le istanze fornite dal componente "superscoped".

Questo può essere fatto con @Subcomponentannotazioni o dipendenze dei componenti. Personalmente preferisco le dipendenze.

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

Oppure puoi usare le dipendenze dei componenti in questo modo

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

Cose importanti da sapere:

  • Un provider con ambito crea un'istanza per quel determinato ambito per ciascun componente . Significa che un componente tiene traccia delle proprie istanze, ma altri componenti non hanno un pool di scopi condiviso o qualche magia. Per avere un'istanza in un determinato ambito, è necessaria un'istanza del componente. Questo è il motivo per cui è necessario fornire l' ApplicationComponentaccesso alle proprie dipendenze con ambito.

  • Un componente può eseguire la sottoscrizione di un solo componente con ambito. Dipendenze di più componenti con ambito non sono consentite.


Un componente può eseguire la sottoscrizione di un solo componente con ambito. Non sono consentite dipendenze di più componenti con ambito (nemmeno se tutte hanno ambiti diversi, anche se penso che sia un bug). non capisco davvero cosa significhi
Damon Yuan

Ma per quanto riguarda il ciclo di vita. Candidato a ActivityComponent per il garbage collector se l'attività viene distrutta?
Sever

Se non lo memorizzi da qualche altra parte, allora sì
EpicPandaForce

1
Quindi, se abbiamo bisogno che il componente e l'oggetto iniettato vivano attraverso l'attività, costruiamo il componente all'interno dell'attività. Se desideriamo solo sopravvivere attraverso un frammento, dovrei costruire un componente all'interno di un frammento, giusto? Dove tieni l'istanza del componente fa ambito?
Tracia

Cosa devo fare se desidero sopravvivere attraverso un'attività specifica?
Tracia,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.