He estado trabajando con dagger2 por un tiempo. Y me confundí si debía crear un componente / módulo propio para cada Actividad / Fragmento. Ayúdame a aclarar esto:
Por ejemplo, tenemos una aplicación y la aplicación tiene alrededor de 50 pantallas. Implementaremos el código siguiendo el patrón MVP y Dagger2 para DI. Supongamos que tenemos 50 actividades y 50 presentadores.
En mi opinión, normalmente deberíamos organizar el código así:
Cree un AppComponent y AppModule que proporcionará todos los objetos que se utilizarán mientras la aplicación esté abierta.
@Module public class AppModule { private final MyApplicationClass application; public AppModule(MyApplicationClass application) { this.application = application; } @Provides @Singleton Context provideApplicationContext() { return this.application; } //... and many other providers } @Singleton @Component( modules = { AppModule.class } ) public interface AppComponent { Context getAppContext(); Activity1Component plus(Activity1Module module); Activity2Component plus(Activity2Module module); //... plus 48 methods for 48 other activities. Suppose that we don't have any other Scope (like UserScope after user login, ....) }
Crear ActivityScope:
@Scope @Documented @Retention(value=RUNTIME) public @interface ActivityScope { }
Cree un componente y un módulo para cada actividad. Normalmente los pondré como clases estáticas dentro de la clase Activity:
@Module public class Activity1Module { public LoginModule() { } @Provides @ActivityScope Activity1Presenter provideActivity1Presenter(Context context, /*...some other params*/){ return new Activity1PresenterImpl(context, /*...some other params*/); } } @ActivityScope @Subcomponent( modules = { Activity1Module.class } ) public interface Activity1Component { void inject(Activity1 activity); // inject Presenter to the Activity } // .... Same with 49 remaining modules and components.
Esos son solo ejemplos muy simples para mostrar cómo implementaría esto.
Pero un amigo mío me acaba de dar otra implementación:
Cree PresenterModule que proporcionará a todos los presentadores:
@Module public class AppPresenterModule { @Provides Activity1Presenter provideActivity1Presentor(Context context, /*...some other params*/){ return new Activity1PresenterImpl(context, /*...some other params*/); } @Provides Activity2Presenter provideActivity2Presentor(Context context, /*...some other params*/){ return new Activity2PresenterImpl(context, /*...some other params*/); } //... same with 48 other presenters. }
Cree AppModule y AppComponent:
@Module public class AppModule { private final MyApplicationClass application; public AppModule(MyApplicationClass application) { this.application = application; } @Provides @Singleton Context provideApplicationContext() { return this.application; } //... and many other provides } @Singleton @Component( modules = { AppModule.class, AppPresenterModule.class } ) public interface AppComponent { Context getAppContext(); public void inject(Activity1 activity); public void inject(Activity2 activity); //... and 48 other methods for 48 other activities. Suppose that we don't have any other Scope (like UserScope after user login, ....) }
Su explicación es: no tiene que crear componentes y módulos para cada actividad. Creo que la idea de mi amigo no es buena en absoluto, pero corríjame si me equivoco. Estas son las razones:
Muchas pérdidas de memoria :
- La aplicación creará 50 presentadores incluso si el usuario solo tiene 2 actividades abiertas.
- Una vez que el usuario cierra una actividad, su presentador seguirá siendo
¿Qué sucede si quiero crear dos instancias de una actividad? (cómo puede crear dos presentadores)
La aplicación tardará mucho en inicializarse (porque tiene que crear muchos presentadores, objetos, ...)
Perdón por una publicación larga, pero por favor ayúdame a aclarar esto para mí y para mi amigo, no puedo convencerlo. Tus comentarios serán muy apreciados.
/ ------------------------------------------------- ---------------------- /
Edite después de hacer una demostración.
Primero, gracias por la respuesta de @pandawarrior. Debería haber creado una demostración antes de hacer esta pregunta. Espero que mi conclusión aquí pueda ayudar a alguien más.
- Lo que ha hecho mi amigo no causa pérdidas de memoria a menos que ponga algún Scope en los métodos Provides. (Por ejemplo @Singleton o @UserScope, ...)
- Podemos crear muchos presentadores, si el método Provides no tiene ningún alcance. (Entonces, mi segundo punto también está mal)
- Dagger creará los presentadores solo cuando sean necesarios. (Entonces, la aplicación no tardará mucho en inicializarse, estaba confundido por Lazy Injection)
Entonces, todas las razones que he dicho anteriormente son en su mayoría incorrectas. Pero eso no significa que debamos seguir la idea de mi amigo, por dos razones:
No es bueno para la arquitectura de la fuente, cuando inicia a todos los presentadores en módulo / componente. (Viola el principio de segregación de interfaz , quizás también el principio de responsabilidad única).
Cuando creamos un componente de alcance, sabremos cuándo se crea y cuándo se destruye, lo cual es un gran beneficio para evitar pérdidas de memoria. Entonces, para cada actividad deberíamos crear un componente con un @ActivityScope. Imaginemos, con la implementación de mis amigos, que nos olvidamos de poner algo de Scope en el método Provider => ocurrirán pérdidas de memoria.
En mi opinión, con una aplicación pequeña (pocas pantallas sin muchas dependencias o con dependencias similares), podríamos aplicar la idea de mis amigos, pero por supuesto que no es recomendable.
Prefiero leer más sobre: ¿Qué determina el ciclo de vida de un componente (gráfico de objeto) en Dagger 2? Alcance de la actividad de Dagger2, ¿cuántos módulos / componentes necesito?
Y una nota más: si desea ver cuándo se destruye el objeto, puede llamar a los del método juntos y el GC se ejecutará inmediatamente:
System.runFinalization();
System.gc();
Si usa solo uno de estos métodos, GC se ejecutará más tarde y puede obtener resultados incorrectos.
ControllerModule
creará uno nuevoPresenter
y luego se inyecta el presentador en elActivity
oFragment
. ¿Alguna opinión sólida a favor o en contra de esto?ControllerComponent
debería inyectarlos. Ya sea que los conecte al interiorControllerModule
o introduzca un módulo adicional, depende de usted. En aplicaciones reales, recomiendo usar un enfoque de múltiples módulos por componente en lugar de poner todo en un solo módulo. Aquí hay un ejemplo deApplicationComponent
, pero el controlador será el mismo: github.com/techyourchance/idocare-android/tree/master/app/src/…ApplicationComponent
todas las dependencias queControllerComponent
puedas usar. Además, el recuento de métodos del código generado será mayor. Todavía no he encontrado una buena razón para usar componentes dependientes.dagger.android
paquete porque lo encuentro mal motivado. Por lo tanto, este ejemplo todavía está muy actualizado y sigue siendo la mejor manera de hacer DI en Android en mi humilde opinión.Algunos de los mejores ejemplos de cómo organizar sus componentes, módulos y paquetes se pueden encontrar en el repositorio de Google Android Architecture Blueprints Github aquí .
Si examina el código fuente allí, puede ver que hay un solo Componente de ámbito de aplicación (con un ciclo de vida de la duración de toda la aplicación) y luego los Componentes de ámbito de actividad separados para la Actividad y el Fragmento correspondiente a una funcionalidad dada en un proyecto. Por ejemplo, existen los siguientes paquetes:
Dentro de cada paquete hay un módulo, componente, presentador, etc. Por ejemplo, dentro
taskdetail
están las siguientes clases:TaskDetailActivity.java TaskDetailComponent.java TaskDetailContract.java TaskDetailFragment.java TaskDetailPresenter.java TaskDetailPresenterModule.java
La ventaja de organizarse de esta manera (en lugar de agrupar todas las actividades en un componente o módulo) es que puede aprovechar los modificadores de accesibilidad de Java y cumplir con el elemento 13. En otras palabras, las clases agrupadas funcionalmente estarán en el mismo empaqueta y se puede aprovechar
protected
ypackage-private
modificadores de accesibilidad para evitar usos no deseados de sus clases.fuente
La primera opción crea un componente de subámbito para cada actividad, donde la actividad es capaz de crear componentes de subámbito que solo proporcionan la dependencia (presentador) para esa actividad en particular.
La segunda opción crea un
@Singleton
componente único que puede proporcionar a los presentadores como dependencias sin ámbito, lo que significa que cuando accede a ellos, crea una nueva instancia del presentador cada vez. (No, no crea una nueva instancia hasta que solicite una).Técnicamente, ninguno de los enfoques es peor que el otro. El primer enfoque no separa a los presentadores por función, sino por capa.
He usado ambos, ambos funcionan y ambos tienen sentido.
La única desventaja de la primera solución (si está usando en
@Component(dependencies={...}
lugar de@Subcomponent
) es que debe asegurarse de que no sea la Actividad la que crea su propio módulo internamente, porque entonces no puede reemplazar las implementaciones del método del módulo con simulaciones. Por otra parte, si usa la inyección de constructor en lugar de la inyección de campo, puede crear la clase directamente con el constructor, dándole directamente simulaciones.fuente
Úselo en
Provider<"your component's name">
lugar de la implementación de componentes simples para evitar pérdidas de memoria y la creación de toneladas de componentes inútiles. Por lo tanto, sus componentes serán creados por lazy cuando llame al método get () ya que no proporciona una instancia del componente, sino solo el proveedor. Por lo tanto, su presentador se aplicará si se llamó a .get () del proveedor. Lea sobre Proveedor aquí y aplique esto. ( Documentación oficial de Dagger )Y otra excelente manera es usar multibinding. De acuerdo con él, debe vincular a sus presentadores en un mapa y crearlos a través de proveedores cuando lo necesite. ( aquí hay documentos sobre multienlace )
fuente
Tu amigo tiene razón, realmente no tienes que crear componentes y módulos para cada actividad. Se supone que Dagger lo ayuda a reducir el código desordenado y hace que sus actividades de Android sean más limpias al delegar instancias de clases a los módulos en lugar de instanciarlas en el método onCreate de Actividades.
Normalmente lo haremos así
public class MainActivity extends AppCompatActivity { Presenter1 mPresenter1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mPresenter1 = new Presenter1(); // you instantiate mPresentation1 in onCreate, imagine if there are 5, 10, 20... of objects for you to instantiate. } }
Tu haces esto en su lugar
public class MainActivity extends AppCompatActivity { @Inject Presenter1 mPresenter1; // the Dagger module take cares of instantiation for your @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); injectThisActivity(); } private void injectThisActivity() { MainApplication.get(this) .getMainComponent() .inject(this); }}
Entonces, escribir demasiadas cosas frustra el propósito de la daga, ¿no? Prefiero crear una instancia de mis presentadores en Actividades si tengo que crear módulos y componentes para cada actividad.
En cuanto a sus preguntas sobre:
1- Pérdida de memoria:
No, a menos que ponga una
@Singleton
anotación a los presentadores que proporcione. Dagger solo creará el objeto siempre que lo hagas@Inject
en la clase de destino`. No creará otros presentadores en su escenario. Puede intentar usar Log para ver si se crean o no.@Module public class AppPresenterModule { @Provides @Singleton // <-- this will persists throughout the application, too many of these is not good Activity1Presenter provideActivity1Presentor(Context context, ...some other params){ Log.d("Activity1Presenter", "Activity1Presenter initiated"); return new Activity1PresenterImpl(context, ...some other params); } @Provides // Activity2Presenter will be provided every time you @Inject into the activity Activity2Presenter provideActivity2Presentor(Context context, ...some other params){ Log.d("Activity2Presenter", "Activity2Presenter initiated"); return new Activity2PresenterImpl(context, ...some other params); } .... Same with 48 others presenters.
}
2- Inyectas dos veces y registras su código hash
//MainActivity.java @Inject Activity1Presenter mPresentation1 @Inject Activity1Presenter mPresentation2 @Inject Activity2Presenter mPresentation3 @Inject Activity2Presenter mPresentation4 //log will show Presentation2 being initiated twice @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); injectThisActivity(); Log.d("Activity1Presenter1", mPresentation1.hashCode()); Log.d("Activity1Presenter2", mPresentation2.hashCode()); //it will shows that both have same hash, it's a Singleton Log.d("Activity2Presenter1", mPresentation3.hashCode()); Log.d("Activity2Presenter2", mPresentation4.hashCode()); //it will shows that both have different hash, hence different objects
3. No, los objetos solo se crearán cuando esté
@Inject
en las actividades, en lugar del inicio de la aplicación.fuente