Od czego zależy cykl życia komponentu (grafu obiektów) w Dagger 2?

135

Próbuję objąć głową zakresy w Dagger 2, a konkretnie cykl życia wykresów z zakresem. Jak utworzyć komponent, który zostanie wyczyszczony po opuszczeniu zakresu.

W przypadku aplikacji na Androida, używając Daggera 1.x, generalnie masz zasięg główny na poziomie aplikacji, który rozszerzyłbyś, aby utworzyć zakres podrzędny na poziomie aktywności.

public class MyActivity {

    private ObjectGraph mGraph;

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

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

Zakres podrzędny istniał tak długo, jak długo zachowałeś do niego odniesienie, co w tym przypadku był cyklem życia Twojej aktywności. Porzucenie referencji w onDestroy zapewniło, że wykres o określonym zakresie może być bezużyteczny.

EDYTOWAĆ

Jesse Wilson niedawno opublikował mea culpa

Dagger 1.0 źle schrzanił nazwy zakresów ... Adnotacja @Singleton jest używana zarówno do wykresów głównych, jak i niestandardowych, więc trudno jest ustalić, jaki jest rzeczywisty zakres rzeczy.

i wszystko inne, co przeczytałem / usłyszałem, wskazuje na Dagger 2 poprawiający sposób działania lunet, ale staram się zrozumieć różnicę. Zgodnie z komentarzem @Kirill Boyarshinov poniżej, cykl życia komponentu lub zależności jest nadal określany, jak zwykle, przez konkretne odniesienia. Czy zatem różnica między celownikami Dagger 1.xi 2.0 jest kwestią jasności semantycznej?

Moje zrozumienie

Sztylet 1.x

Zależności były albo @Singletonnie. Dotyczyło to również zależności w grafie głównym i podgrafach, co prowadziło do niejednoznaczności co do tego, do którego wykresu zależność została przypisana (zobacz In Dagger są singletonami w pod-grafie zapisanym w pamięci podręcznej, czy też zawsze zostaną odtworzone, gdy nowy podgraf aktywności jest zbudowany? )

Sztylet 2.0

Niestandardowe zakresy umożliwiają tworzenie jasnych semantycznie zakresów, ale są funkcjonalnym odpowiednikiem stosowania @Singletonw 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;
    }

}

Na wynos jest to, że używanie @PerActivityprzekazuje twoje zamiary dotyczące cyklu życia tego komponentu, ale ostatecznie możesz używać komponentu w dowolnym miejscu / czasie. Jedyną obietnicą Daggera jest to, że dla danego komponentu metody z adnotacjami zakresu zwrócą pojedynczą instancję. Zakładam również, że Dagger 2 używa adnotacji zakresu na komponencie, aby sprawdzić, czy moduły zapewniają tylko zależności, które są albo w tym samym zakresie, albo bez zakresu.

W podsumowaniu

Zależności są nadal pojedyncze lub inne, ale @Singletonsą teraz przeznaczone dla pojedynczych wystąpień na poziomie aplikacji, a niestandardowe zakresy są preferowaną metodą opisywania pojedynczych zależności o krótszym cyklu życia.

Deweloper jest odpowiedzialny za zarządzanie cyklem życia komponentów / zależności poprzez usuwanie odwołań, które nie są już potrzebne i odpowiada za zapewnienie, że komponenty są tworzone tylko raz w zakresie, dla którego są przeznaczone, ale niestandardowe adnotacje zakresu ułatwiają identyfikację tego zakresu .

Pytanie za 64 000 $ *

Czy moje rozumienie zakresów i cykli życia Dagger 2 jest prawidłowe?

* Właściwie nie jest to pytanie za 64 000 $.

Enrico
źródło
5
Nic nie przegapiłeś. Zarządzanie cyklem życia każdego komponentu odbywa się ręcznie. Z własnego doświadczenia wiem, że to samo było w Dagger 1. Podczas tworzenia podgrafów obiekt ObjectGraph na poziomie aplikacji przy użyciu plus()odniesienia do nowego wykresu był przechowywany w Aktywności i był powiązany z jego cyklem życia (dereferencjowany w onDestroy). Jeśli chodzi o zakresy, zapewniają one, że implementacje komponentów są generowane bez błędów w czasie kompilacji, przy spełnieniu każdej zależności. Więc to nie tylko w celach dokumentacyjnych. Zobacz przykład z tego wątku .
Kirill Boyarshinov
1
Żeby było jasne, metody dostawcy „bez zakresu” zwracają nowe instancje przy każdym wstrzyknięciu?
user1923613
2
Dlaczego ustawiasz składnik = null; w onDestroy ()?
Marian Paździoch

Odpowiedzi:

70

Co do twojego pytania

Od czego zależy cykl życia komponentu (grafu obiektów) w Dagger 2?

Krótka odpowiedź brzmi: ty to określasz . Twoim komponentom można nadać zakres, taki jak

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

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

Są one przydatne w dwóch przypadkach:

  • Walidacja zakresu: składnik może mieć tylko dostawców bez zakresu lub dostawców z zakresem o takim samym zakresie jak składnik.

.

@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();
    }
}
  • Pozwala na podzakres zależności w zakresie, umożliwiając w ten sposób utworzenie komponentu „z zakresem”, który używa instancji dostarczonych z komponentu „z zakresem”.

Można to zrobić za pomocą @Subcomponentadnotacji lub zależności komponentów. Osobiście wolę zależności.

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

Lub możesz użyć takich zależności komponentów

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

Ważne informacje:

  • Dostawca z zakresem tworzy jedno wystąpienie dla tego danego zakresu dla każdego składnika . Oznacza to, że komponent śledzi swoje własne wystąpienia, ale inne komponenty nie mają wspólnej puli zasięgu ani jakiejś magii. Aby mieć jedną instancję w danym zakresie, potrzebujesz jednej instancji komponentu. Dlatego należy zapewnić ApplicationComponentdostęp do jego własnych zależności w zakresie.

  • Komponent może obejmować tylko jeden zakres. Zależności wielu składników o określonym zakresie są niedozwolone.

EpicPandaForce
źródło
Komponent może obejmować tylko jeden zakres. Zależności komponentów o wielu zakresach są niedozwolone (nawet jeśli wszystkie mają różne zakresy, chociaż myślę, że to błąd). nie bardzo rozumiem, co to znaczy
Damon Yuan
Ale co z cyklem życia. Czy komponent ActivityComponent będzie kandydatem do odśmiecania pamięci, jeśli działanie zostanie zniszczone?
Sever
Jeśli nie przechowujesz go gdzie indziej, to tak
EpicPandaForce
1
Więc jeśli potrzebujemy komponentu i wstrzykniętego obiektu na żywo poprzez Activity, budujemy komponent wewnątrz Activity. Jeśli chcemy tylko przetrwać fragment, powinienem zbudować komponent wewnątrz fragmentu, prawda? Gdzie trzymasz wystąpienie komponentu sprawia, że ​​zasięg?
Tracki
Co powinienem zrobić, jeśli chcę, aby przetrwała określoną aktywność?
Tracki