Od jakiegoś czasu pracuję z dagger2. I pomyliłem się, tworząc własny komponent / moduł dla każdego działania / fragmentu. Pomóż mi to wyjaśnić:
Na przykład mamy aplikację, która ma około 50 ekranów. Zaimplementujemy kod według wzorca MVP i Dagger2 dla DI. Załóżmy, że mamy 50 zajęć i 50 prezenterów.
Moim zdaniem zwykle powinniśmy zorganizować kod w ten sposób:
Utwórz AppComponent i AppModule, które udostępnią wszystkie obiekty, które będą używane, gdy aplikacja jest otwarta.
@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, ....) }
Utwórz zakres działania:
@Scope @Documented @Retention(value=RUNTIME) public @interface ActivityScope { }
Utwórz komponent i moduł dla każdego działania. Zazwyczaj umieszczam je jako klasy statyczne w klasie 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.
To tylko bardzo proste przykłady, które pokazują, jak bym to zaimplementował.
Ale mój przyjaciel właśnie dał mi inną implementację:
Utwórz PresenterModule, który zapewni wszystkim prezenterom:
@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. }
Utwórz AppModule i 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, ....) }
Jego wyjaśnienie brzmi: nie musi tworzyć komponentów i modułów dla każdej czynności. Myślę, że pomysł moich przyjaciół absolutnie nie jest dobry, ale proszę mnie poprawić, jeśli się mylę. Oto powody:
Wiele wycieków pamięci :
- Aplikacja utworzy 50 osób prowadzących, nawet jeśli użytkownik ma otwarte tylko 2 działania.
- Po zamknięciu działania przez użytkownika jego osoba prowadząca pozostanie
Co się stanie, jeśli chcę utworzyć dwa wystąpienia jednego działania? (jak może stworzyć dwóch prezenterów)
Zainicjowanie aplikacji zajmie dużo czasu (ponieważ musi utworzyć wiele osób prowadzących, obiektów itp.)
Przepraszam za długi post, ale pomóż mi to wyjaśnić dla mnie i mojego przyjaciela, nie mogę go przekonać. Twoje komentarze będą bardzo mile widziane.
/ ------------------------------------------------- ---------------------- /
Edytuj po wykonaniu demonstracji.
Po pierwsze, dziękuję za odpowiedź @pandawarrior. Powinienem był stworzyć Demo, zanim zadałem to pytanie. Mam nadzieję, że mój wniosek może pomóc komuś innemu.
- To, co zrobił mój przyjaciel, nie powoduje wycieków pamięci, chyba że doda zakres do metod Provides. (Na przykład @Singleton lub @UserScope, ...)
- Możemy stworzyć wielu prezenterów, jeśli metoda Provides nie ma żadnego zakresu. (Więc moja druga uwaga też jest błędna)
- Sztylet stworzy prezenterów tylko wtedy, gdy będą potrzebni. (Tak więc inicjalizacja aplikacji nie zajmie dużo czasu, byłem zdezorientowany przez Lazy Injection)
Zatem wszystkie powody, które powiedziałem powyżej, są w większości błędne. Ale to nie znaczy, że powinniśmy podążać za moim przyjacielem z dwóch powodów:
Nie jest to dobre dla architektury źródła, kiedy on inicjuje wszystkich prezenterów w module / komponencie. (Narusza to zasadę segregacji interfejsów , być może także zasadę pojedynczej odpowiedzialności ).
Kiedy tworzymy komponent zakresu, będziemy wiedzieć, kiedy zostanie utworzony i kiedy zostanie zniszczony, co jest ogromną korzyścią dla uniknięcia wycieków pamięci. Dlatego dla każdego działania powinniśmy utworzyć komponent z @ActivityScope. Wyobraźmy sobie, że z implementacją moich znajomych zapomnieliśmy umieścić jakiś zakres w metodzie Provider => wystąpią wycieki pamięci.
Moim zdaniem przy małej aplikacji (zaledwie kilka ekranów bez wielu zależności lub z podobnymi zależnościami) moglibyśmy zastosować pomysł znajomych, ale oczywiście nie jest to zalecane.
Wolisz przeczytać więcej na temat: Co decyduje o cyklu życia komponentu (grafu obiektowego) w Dagger 2? Zakres działalności Dagger2, ile modułów / komponentów potrzebuję?
I jeszcze jedna uwaga: jeśli chcesz zobaczyć, kiedy obiekt zostanie zniszczony, możesz wywołać te z metody razem, a GC uruchomi się natychmiast:
System.runFinalization();
System.gc();
Jeśli użyjesz tylko jednej z tych metod, GC uruchomi się później i możesz uzyskać nieprawidłowe wyniki.
ControllerModule
utworzy nowy,Presenter
a następnie prezenter zostanie wstrzyknięty wActivity
lubFragment
. Masz jakąś solidną opinię za lub przeciw?