web-dev-qa-db-fra.com

Qu'est-ce qui détermine le cycle de vie d'un composant (graphe d'objet) dans Dagger 2?

J'essaie de comprendre les portées de Dagger 2, en particulier le cycle de vie des graphiques portés. Comment créez-vous un composant qui sera nettoyé lorsque vous quitterez l'oscilloscope?.

Dans le cas d'une application Android, à l'aide de Dagger 1.x, vous disposez généralement d'une étendue racine au niveau de l'application que vous étendriez pour créer une étendue enfant au niveau de l'activité.

public class MyActivity {

    private ObjectGraph mGraph;

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

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

La portée enfant existait tant que vous y gardiez une référence, ce qui était dans ce cas le cycle de vie de votre activité. La suppression de la référence dans onDestroy garantissait que le graphe couvert pouvait être ramassé librement.

[~ # ~] éditer [~ # ~]

Jesse Wilson a récemment publié un mea culpa

Dagger 1.0 a mal bousillé ses noms d'étendue ... L'annotation @Singleton est utilisée à la fois pour les graphiques racine et les graphiques personnalisés. Il est donc difficile de déterminer quelle est l'étendue réelle d'une chose.

et tout ce que j'ai lu/entendu indique que Dagger 2 devrait améliorer le fonctionnement des oscilloscopes, mais j'ai du mal à comprendre la différence. Selon le commentaire de @Kirill Boyarshinov ci-dessous, le cycle de vie d'un composant ou d'une dépendance est toujours déterminé, comme d'habitude, par des références concrètes. La différence entre les portées Dagger 1.x et 2.0 est-elle donc purement une question de clarté sémantique?

Ma compréhension

Dague 1.x

Les dépendances étaient soit @Singleton ou pas. Cela était également vrai des dépendances dans le graphe racine et les sous-graphes, ce qui a créé une ambiguïté quant au graphe auquel la dépendance était liée (voir Les poignards sont des singletons dans le sous-graphe mis en cache ou seront-ils toujours recréés quand le sous-graphique d'activité est construit? )

Dague 2.0

Les étendues personnalisées vous permettent de créer des étendues sémantiquement claires, mais sont fonctionnellement équivalentes à l'application de @Singleton dans Dague 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;
    }

}

La conclusion étant que l'utilisation de @PerActivity communique votre intention en ce qui concerne le cycle de vie de ce composant, mais vous pouvez finalement utiliser le composant n'importe où et à tout moment. La seule promesse de Dagger est que, pour un composant donné, les méthodes annotées par portée renverront une seule instance. Je suppose également que Dagger 2 utilise l'annotation de portée sur le composant pour vérifier que les modules ne fournissent que des dépendances qui sont dans la même portée ou qui ne sont pas étendues.

En résumé

Les dépendances sont toujours singleton ou non singleton, mais @Singleton est maintenant destiné aux instances de singleton au niveau de l'application et les étendues personnalisées sont la méthode recommandée pour annoter les dépendances de singleton avec un cycle de vie plus court.

Le développeur est responsable de la gestion du cycle de vie des composants/dépendances en supprimant les références devenues inutiles, et de la création unique des composants dans l'étendue pour laquelle ils ont été conçus, mais les annotations d'étendue personnalisées facilitent l'identification de cette étendue. .

La question à 64k $ *

Ma compréhension des portées et des cycles de vie de Dagger 2 est-elle correcte?

* Pas vraiment une question à 64 000 $.

125
Enrico

Quant à ta question

Qu'est-ce qui détermine le cycle de vie d'un composant (graphe d'objet) dans Dagger 2?

La réponse courte est vous le déterminez. Vos composants peuvent avoir une portée, telle que

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

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

Celles-ci vous sont utiles pour deux choses:

  • Validation de la portée: un composant ne peut avoir que des fournisseurs non étendus, ou des fournisseurs étendus de la même portée que votre composant.

.

@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();
    }
}
  • Permet de sous-définir la portée de vos dépendances, vous permettant ainsi de créer un composant "sous-exploité" qui utilise les instances fournies par le composant "super-optimisé".

Cela peut être fait avec @Subcomponent annotation ou dépendances de composants. Personnellement, je préfère les dépendances.

@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 vous pouvez utiliser des dépendances de composants comme

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

Choses importantes à savoir:

  • Un fournisseur ciblé crée une instance pour cette étendue donnée pour chaque composant. Cela signifie qu'un composant garde la trace de ses propres instances, mais que d'autres composants n'ont pas de pool de domaines partagés ni de magie. Pour avoir une instance dans une portée donnée, vous avez besoin d'une instance du composant. C'est pourquoi vous devez fournir le ApplicationComponent pour accéder à ses propres dépendances définies.

  • Un composant ne peut sous-localiser qu'un seul composant couvert. Les dépendances de plusieurs composants scoped ne sont pas autorisées.

64
EpicPandaForce