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?ControllerComponent
powinien je wstrzyknąć. Od CiebieControllerModule
zależy, czy podłączysz je do środka , czy wprowadzisz dodatkowy moduł. W prawdziwych aplikacjach radzę stosować podejście wielomodułowe na komponent zamiast umieszczać wszystko w jednym module. Oto przykład dlaApplicationComponent
, ale kontroler będzie taki sam: github.com/techyourchance/idocare-android/tree/master/app/src/…ApplicationComponent
wszystkich zależnościach, któreControllerComponent
mogą używać. Również liczba metod wygenerowanego kodu będzie wyższa. Nie znalazłem jeszcze dobrego powodu, aby używać komponentów zależnych.dagger.android
pakietu, ponieważ uważam, że jest to źle zmotywowane. Dlatego ten przykład jest nadal bardzo aktualny i nadal jest najlepszym sposobem na zrobienie DI w Android IMHO.Niektóre z najlepszych przykładów organizacji komponentów, modułów i pakietów można znaleźć w repozytorium Google Android Architecture Blueprints Github tutaj .
Jeśli przejrzysz tam kod źródłowy, zobaczysz, że istnieje jeden składnik o zakresie aplikacji (z cyklem życia całej aplikacji), a następnie oddzielne składniki o zakresie działania dla działania i fragmentu odpowiadające danej funkcji w projekt. Na przykład istnieją następujące pakiety:
W każdym pakiecie znajduje się moduł, komponent, prezenter itp. Na przykład wewnątrz
taskdetail
znajdują się następujące klasy:TaskDetailActivity.java TaskDetailComponent.java TaskDetailContract.java TaskDetailFragment.java TaskDetailPresenter.java TaskDetailPresenterModule.java
Zaletą takiego zorganizowania (zamiast grupowania wszystkich działań w jednym komponencie lub module) jest to, że można skorzystać z modyfikatorów dostępności Java i wypełnić Efektywny element Java 13. Innymi słowy, funkcjonalnie zgrupowane klasy będą w tym samym pakiet można wykorzystać
protected
ipackage-private
modyfikatorów dostępu , aby zapobiec niezamierzonym zwyczaje swoich klasach.źródło
Pierwsza opcja tworzy komponent o obniżonym zakresie dla każdego działania, przy czym działanie to może tworzyć komponenty o obniżonym zakresie, które zapewniają tylko zależność (prezentera) dla tego konkretnego działania.
Druga opcja tworzy pojedynczy
@Singleton
komponent, który jest w stanie dostarczyć prezenterów jako zależności bez zakresu, co oznacza, że kiedy uzyskujesz do nich dostęp, za każdym razem tworzysz nową instancję prezentera. (Nie, nie tworzy nowej instancji, dopóki jej nie zażądasz).Technicznie żadne podejście nie jest gorsze od drugiego. Pierwsze podejście nie rozdziela prezenterów według funkcji, ale według warstw.
Użyłem obu, oba działają i oba mają sens.
Jedyną wadą pierwszego rozwiązania (jeśli używasz
@Component(dependencies={...}
zamiast niego@Subcomponent
) jest to, że musisz upewnić się, że to nie działanie tworzy wewnętrznie własny moduł, ponieważ wtedy nie możesz zastąpić implementacji metod modułu makietami. Z drugiej strony, jeśli użyjesz iniekcji konstruktora zamiast iniekcji pola, możesz po prostu utworzyć klasę bezpośrednio za pomocą konstruktora, bezpośrednio nadając jej mocks.źródło
Używaj
Provider<"your component's name">
zamiast prostych implementacji komponentów, aby uniknąć wycieków pamięci i tworzenia ton bezużytecznych komponentów. Dlatego twoje komponenty będą tworzone przez leniwość, kiedy wywołasz metodę get (), ponieważ nie podajesz instancji komponentu, a zamiast tego tylko dostawcę. W ten sposób prezenter zostanie zastosowany, jeśli wywołano .get () dostawcy. Przeczytaj o Dostawcy tutaj i zastosuj to. ( Oficjalna dokumentacja sztyletu )Innym świetnym sposobem jest użycie wielowiązania. Zgodnie z nią powinieneś powiązać swoich prezenterów z mapą i tworzyć je przez dostawców, kiedy tego potrzebujesz. ( tutaj jest dokumentacja o wielokrotnym wiązaniu )
źródło
Twój znajomy ma rację, tak naprawdę nie musisz tworzyć komponentów i modułów dla każdej czynności. Dagger ma pomóc ci zredukować niechlujny kod i sprawić, że twoje działania w Androidzie będą czystsze, delegując wystąpienia klas do modułów, zamiast tworzyć je w metodzie onCreate Działania.
Normalnie zrobimy to w ten sposób
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. } }
Zamiast tego zrób to
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); }}
Więc pisanie zbyt wielu rzeczy może pokrzyżować cel sztyletu, nie? Raczej tworzę wystąpienia moich prezenterów w działaniach, jeśli muszę tworzyć moduły i komponenty dla każdego działania.
Jeśli chodzi o pytania dotyczące:
1- Wyciek pamięci:
Nie, chyba że
@Singleton
dodasz adnotację do prowadzących, których dostarczasz. Dagger stworzy obiekt tylko wtedy, gdy zrobisz to@Inject
w klasie docelowej. Nie utworzy innych prezenterów w Twoim scenariuszu. Możesz spróbować użyć dziennika, aby sprawdzić, czy zostały utworzone, czy nie.@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- Wstrzykujesz dwa razy i rejestrujesz ich kod skrótu
//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. Nie, obiekty zostaną utworzone tylko wtedy, gdy wejdziesz
@Inject
do działań, zamiast inicjalizacji aplikacji.źródło