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

134

J'essaie de comprendre les portées de Dagger 2, en particulier le cycle de vie des graphiques scoped. Comment créer un composant qui sera nettoyé lorsque vous quitterez la portée.

Dans le cas d'une application Android, en utilisant Dagger 1.x, vous avez généralement 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;
    }
}

L'étendue enfant existait tant que vous en gardiez une référence, ce qui dans ce cas était le cycle de vie de votre activité. La suppression de la référence dans onDestroy a garanti que le graphe de portée était libre d'être récupéré.

ÉDITER

Jesse Wilson a récemment publié un mea culpa

Dagger 1.0 a mal vissé ses noms de portée ... 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 la portée réelle d'une chose.

et tout ce que j'ai lu / entendu indique que Dagger 2 améliore 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 purement une question de clarté sémantique?

Ma compréhension

Dague 1.x

Les dépendances étaient soit @Singletonou non. Cela était également vrai pour les dépendances dans le graphe racine et les sous-graphes, conduisant à une ambiguïté quant au graphe auquel la dépendance était liée (voir Dans Dagger sont des singletons dans le sous-graphe mis en cache ou seront-ils toujours recréés lorsqu'un nouveau sous-graphe d'activité est construit? )

Dague 2.0

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

}

Ce qu'il faut retenir, c'est que l'utilisation @PerActivitycommunique votre intention concernant le cycle de vie de ce composant, mais en fin de compte, vous pouvez utiliser le composant n'importe où / n'importe quand. La seule promesse de Dagger est que, pour un composant donné, les méthodes annotées de 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 non.

En résumé

Les dépendances sont toujours singleton ou non singleton, mais elles @Singletonsont désormais destinées aux instances de singleton au niveau de l'application et les étendues personnalisées sont la méthode préféré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 qui ne sont plus nécessaires et de s'assurer que les composants ne sont créés qu'une seule fois dans la portée pour laquelle ils sont destinés, mais les annotations de portée personnalisées facilitent l'identification de cette portée. .

La question à 64 000 $ *

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

* Pas vraiment une question à 64 000 $.

Enrico
la source
5
Vous n'avez rien manqué. La gestion du cycle de vie de chaque composant est manuelle. D'après ma propre expérience, la même chose était dans Dagger 1 aussi. Lors du sous-graphisme, un objet ObjectGraph de niveau Application utilisant une plus()référence à un nouveau graphique était stocké dans Activity et était lié à son cycle de vie (déréférencé dans onDestroy). Quant aux étendues, elles garantissent que vos implémentations de composants sont générées sans erreur au moment de la compilation, avec chaque dépendance satisfaite. Ce n'est donc pas seulement à des fins de documentation. Découvrez un exemple de ce fil .
Kirill Boyarshinov
1
Juste pour être clair à ce sujet, les méthodes de fournisseur "sans portée" renvoient de nouvelles instances à chaque injection?
user1923613
2
Pourquoi définissez-vous component = null; dans onDestroy ()?
Marian Paździoch

Réponses:

70

Quant à votre question

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

La réponse courte est que 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 l'étendue: un composant ne peut avoir que des fournisseurs non étendus, ou des fournisseurs étendus de même étendue 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-étendre vos dépendances étendues, vous permettant ainsi de créer un composant «sous-scopé» qui utilise les instances fournies à partir du composant «superscoped».

Cela peut être fait avec des @Subcomponentannotations ou des 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 ça

@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 de portée crée une instance pour cette portée donnée pour chaque composant . Cela signifie qu'un composant garde la trace de ses propres instances, mais les autres composants n'ont pas de pool de portée partagée ou 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 ApplicationComponentpour accéder à ses propres dépendances étendues.

  • Un composant ne peut sous-étendre qu'un seul composant de portée. Plusieurs dépendances de composants étendues ne sont pas autorisées.

EpicPandaForce
la source
Un composant ne peut sous-étendre qu'un seul composant de portée. Les dépendances de composants à portée multiple ne sont pas autorisées (même si elles ont toutes des portées différentes, même si je pense que c'est un bogue). pas vraiment compris ce que cela signifie
Damon Yuan
Mais qu'en est-il du cycle de vie. ActivityComponent sera-t-il candidat au ramasse-miettes si l'activité est détruite?
Sever
Si vous ne le stockez pas ailleurs, alors oui
EpicPandaForce
1
Donc, si nous avons besoin d'un composant et d'un objet injecté en direct via Activity, nous construisons un composant dans Activity. Si nous souhaitons seulement survivre à travers un fragment, je devrais construire un composant à l'intérieur d'un fragment, non? Où vous gardez l'instance de composant fait la portée?
Thracian
Que dois-je faire si je souhaite qu'il survienne grâce à une activité spécifique?
Thracian