Enrique
Estoy tratando de entender los alcances en Dagger 2, específicamente el ciclo de vida de los gráficos con alcance. ¿Cómo se crea un componente que se limpiará cuando abandone el alcance?
En el caso de una aplicación de Android, al usar Dagger 1.x, generalmente tiene un ámbito raíz en el nivel de la aplicación que extendería para crear un ámbito secundario en el nivel de la 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 alcance secundario existió siempre que mantuvo 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 grafo de ámbito fuera libre para ser recolectado como basura.
EDITAR
Jesse Wilson publicó recientemente una mea culpa
Dagger 1.0 arruinó gravemente sus nombres de alcance… La anotación @Singleton se usa tanto para gráficos de raíz como para gráficos personalizados, por lo que es complicado averiguar cuál es el alcance real de una cosa.
y todo lo demás que he leído/escuchado apunta a que Dagger 2 mejora la forma en que funcionan los visores, 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 de Dagger 1.x y 2.0 es puramente una cuestión de claridad semántica?
Mi entendimiento
Daga 1.x
Las dependencias eran @Singleton
O no. Esto era igualmente cierto para las dependencias en el gráfico raíz y los subgráficos, lo que generaba ambigüedad sobre a qué gráfico estaba vinculada la dependencia (consulte En Dagger, los Singleton dentro del subgráfico se almacenan en caché o siempre se volverán a crear cuando se cree un nuevo subgráfico de actividad). ¿esta construido?)
Daga 2.0
Los ámbitos personalizados le permiten crear ámbitos semánticamente claros, pero son funcionalmente equivalentes a aplicar @Singleton
en Daga 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 usar @PerActivity
comunica su intención con respecto al ciclo de vida de este componente, pero en última instancia, puede usar el componente en cualquier lugar y en cualquier momento. La única promesa de Dagger es que, para un componente dado, los métodos anotados de alcance devolverán una sola instancia. También asumo 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 @Singleton
ahora está diseñado para instancias de singleton a nivel de aplicación y los ámbitos personalizados son el método preferido para anotar dependencias de singleton con un ciclo de vida más corto.
El desarrollador es responsable de administrar el ciclo de vida de los componentes/dependencias eliminando las referencias que ya no son necesarias y es responsable de garantizar que los componentes solo se creen una vez en el ámbito para el que están destinados, pero las anotaciones de ámbito personalizadas facilitan la identificación de ese ámbito. .
La pregunta de $64k*
¿Es correcta mi comprensión de los alcances y ciclos de vida de Dagger 2?
* En realidad no es una pregunta de $64’000.
EpicPandaFuerza
En cuanto a tu pregunta
¿Qué determina el ciclo de vida de un componente (gráfico de objetos) en Dagger 2?
La respuesta corta es tu lo determinas. A sus componentes se les puede dar 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 realizar un subámbito de las dependencias con ámbito, lo que le permite crear un componente “subámbito” que utiliza las instancias proporcionadas del componente “superámbito”.
Esto se puede hacer con @Subcomponent
anotación o dependencias de componentes. Yo 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 dependencias de componentes así
@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 que debe 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 mágico. Para tener una instancia en un ámbito determinado, necesita una instancia del componente. Es por eso que debe proporcionar la
ApplicationComponent
para acceder a sus propias dependencias de ámbito. -
Un componente puede incluir solo un componente con ámbito. No se permiten dependencias de componentes de ámbito múltiple.
-
Un componente puede incluir solo un componente con ámbito. No se permiten dependencias de componentes de ámbito múltiple (ni siquiera si todos tienen ámbitos diferentes, aunque creo que es un error). realmente no entiendo lo que significa
– Damon Yuan
4 de mayo de 2017 a las 5:25
-
Pero, ¿qué pasa con el ciclo de vida? ¿Será ActivityComponent candidato a recolector de basura si la actividad se destruye?
– Cortar
20 de diciembre de 2017 a las 22:26
-
Si no lo guardas en otro lugar, entonces sí.
– EpicPandaForce
21 de diciembre de 2017 a las 16:16
-
Entonces, si necesitamos un componente y un objeto inyectado en vivo a través de la Actividad, construimos el componente dentro de la Actividad. Si solo deseamos sobrevivir a través de un Fragmento, debería construir un componente dentro del fragmento, ¿verdad? ¿Dónde guarda la instancia del componente hace alcance?
– tracio
9 de julio de 2018 a las 12:03
-
@Thracian, ¿obtuvo respuesta/experiencia a su pregunta? Creé ámbitos para mis módulos y subcomponentes con actividad inyectada, pero parece que uno de los objetos proporcionados está viviendo más allá de su supuesto ciclo de vida.
– san jab
12 de marzo de 2019 a las 18:07
No te perdiste nada. La gestión del ciclo de vida de cada componente es manual. Desde mi propia experiencia, lo mismo sucedió en Dagger 1 también. Al subgrafiar el objeto ObjectGraph de nivel de aplicación usando
plus()
la referencia al nuevo gráfico se almacenó en Actividad y se vinculó a su ciclo de vida (sin referencia enonDestroy
). En cuanto a los ámbitos, aseguran que las implementaciones de sus componentes se generen sin errores en el momento de la compilación, con todas las dependencias satisfechas. Por lo tanto, no solo para fines de documentación. Echa un vistazo a algunos ejemplos de este hilo.– Kirill Boyarshinov
10 de febrero de 2015 a las 11:15
Solo para aclarar esto, ¿los métodos de proveedor “sin ámbito” devuelven nuevas instancias en cada inyección?
– usuario1923613
21 de julio de 2015 a las 3:53
¿Por qué estableces componente = nulo; en onDestroy()?
– Marian Paździoch
29 de abril de 2016 a las 7:20