¿Qué determina el ciclo de vida de un componente (gráfico de objeto) en Dagger 2?

134

Estoy tratando de comprender los ámbitos en Dagger 2, específicamente el ciclo de vida de los gráficos de ámbito. ¿Cómo se crea un componente que se limpiará cuando abandone el ámbito?

En el caso de una aplicación de Android, al usar Dagger 1.x generalmente tiene un alcance raíz en el nivel de la aplicación que extendería para crear un alcance secundario en el nivel de actividad.

public class MyActivity {

    private ObjectGraph mGraph;

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

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

El ámbito secundario existió siempre que mantuviera una referencia a él, que en este caso fue el ciclo de vida de su Actividad. Dejar caer la referencia en onDestroy aseguró que el gráfico de alcance fuera libre para ser recolectado.

EDITAR

Jesse Wilson recientemente publicó un mea culpa

Dagger 1.0 jodió mal sus nombres de alcance ... La anotación @Singleton se usa tanto para gráficos raíz como para gráficos personalizados, por lo que es difícil descubrir cuál es el alcance real de una cosa.

y todo lo demás que he leído / escuchado apunta a Dagger 2 mejorando la forma en que funcionan los ámbitos, pero me cuesta entender la diferencia. Según el comentario de @Kirill Boyarshinov a continuación, el ciclo de vida de un componente o dependencia todavía está determinado, como de costumbre, por referencias concretas. Entonces, ¿la diferencia entre los alcances Dagger 1.xy 2.0 es puramente una cuestión de claridad semántica?

Mi entendimiento

Daga 1.x

Las dependencias eran @Singletono no. Esto fue igualmente cierto para las dependencias en el gráfico raíz y los subgrafos, lo que lleva a la ambigüedad sobre a qué gráfico estaba vinculada la dependencia (ver En Dagger son Singletons dentro del sub-gráfico en caché o siempre se recrearán cuando una nueva sub-gráfica de actividad está construido? )

Daga 2.0

Los ámbitos personalizados le permiten crear ámbitos semánticamente claros, pero son funcionalmente equivalentes a la aplicación @Singletonen 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;
    }

}

La conclusión es que el uso @PerActivitycomunica su intención con respecto al ciclo de vida de este componente, pero en última instancia, puede usar el componente en cualquier lugar / en cualquier momento. La única promesa de Dagger es que, para un componente dado, los métodos anotados en el alcance devolverán una sola instancia. También supongo que Dagger 2 usa la anotación de alcance en el componente para verificar que los módulos solo proporcionen dependencias que estén en el mismo alcance o sin alcance.

En resumen

Las dependencias siguen siendo singleton o no singleton, pero @Singletonahora están destinadas a instancias singleton a nivel de aplicación y los ámbitos personalizados son el método preferido para anotar dependencias singleton con un ciclo de vida más corto.

El desarrollador es responsable de administrar el ciclo de vida de los componentes / dependencias eliminando referencias que ya no son necesarias y responsable de garantizar que los componentes solo se creen una vez en el alcance para el que están destinados, pero las anotaciones de alcance personalizadas hacen que sea más fácil identificar ese alcance .

La pregunta de $ 64k *

¿Es correcto mi comprensión de los ámbitos y ciclos de vida de Dagger 2?

* No es realmente una pregunta de $ 64'000.

Enrico
fuente
55
No te perdiste nada. La gestión del ciclo de vida de cada componente es manual. Desde mi propia experiencia, lo mismo ocurrió en Dagger 1 también. Cuando se subgrafiaba, el objeto ObjectGraph de nivel de aplicación con plus()referencia a un nuevo gráfico se almacenaba en Activity y estaba vinculado a su ciclo de vida (desreferenciado onDestroy). En cuanto a los ámbitos, aseguran que las implementaciones de sus componentes se generen sin errores en el momento de la compilación, con cada dependencia satisfecha. Por lo tanto, no solo con fines de documentación. Mira algunos ejemplos de este hilo .
Kirill Boyarshinov
1
Para ser claros, ¿los métodos de proveedor "sin ámbito" devuelven nuevas instancias en cada inyección?
user1923613
2
¿Por qué establece componente = nulo; en onDestroy ()?
Marian Paździoch

Respuestas:

70

En cuanto a su pregunta

¿Qué determina el ciclo de vida de un componente (gráfico de objeto) en Dagger 2?

La respuesta corta es que tú lo determinas . Sus componentes pueden tener un alcance, como

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

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

Estos son útiles para usted por dos cosas:

  • Validación del alcance: un componente solo puede tener proveedores sin alcance o proveedores con alcance del mismo alcance que su 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-ámbito de sus dependencias de ámbito, lo que le permite crear un componente "sub-ámbito" que utiliza las instancias proporcionadas desde el componente "super-ámbito".

Esto se puede hacer con @Subcomponentanotaciones o dependencias de componentes. Personalmente prefiero las dependencias.

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

O puede usar las dependencias de los componentes de esta manera

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

Cosas importantes a saber:

  • Un proveedor con ámbito crea una instancia para ese ámbito dado para cada componente . Lo que significa que un componente realiza un seguimiento de sus propias instancias, pero otros componentes no tienen un grupo de alcance compartido o algo de magia. Para tener una instancia en un ámbito determinado, necesita una instancia del componente. Es por eso que debe proporcionar el ApplicationComponentpara acceder a sus propias dependencias de ámbito.

  • Un componente puede subscope solo un componente de ámbito. No se permiten dependencias de componentes de ámbito múltiple.

EpicPandaForce
fuente
Un componente puede subscope solo un componente de ámbito. No se permiten dependencias de componentes de ámbito múltiple (ni siquiera si todas tienen ámbitos diferentes, aunque creo que es un error). realmente no entiendo lo que significa
Damon Yuan
Pero, ¿qué pasa con el ciclo de vida? ¿Será ActivityComponent candidato para recolector de basura si se destruye la actividad?
Sever
Si no lo almacena en otro lugar, sí
EpicPandaForce
1
Entonces, si necesitamos componentes y objetos inyectados en vivo a través de Activity, construimos componentes dentro de Activity. Si solo deseamos sobrevivir a través de un Fragmento, debería construir un componente dentro del fragmento, ¿verdad? ¿Dónde guarda la instancia de componente hace alcance?
Tracio
¿Qué debo hacer si deseo que sobreviva a través de una actividad específica?
Tracio