Dagger & Android
Jedną z podstawowych zalet Dagger 2 w porównaniu z większością innych frameworków wstrzykiwania zależności jest to, że jego ściśle generowana implementacja (bez refleksji) oznacza, że może być używany w aplikacjach na Androida. Jednakże, nadal istnieją pewne rozważania, które należy poczynić podczas używania Daggera w aplikacjach na Androida.
Filozofia
Kod napisany dla Androida jest kodem źródłowym Javy, ale często jest zupełnie inny pod względem stylu. Zazwyczaj takie różnice istnieją w celu dostosowania się do unikalnych względów wydajnościowych platformy mobilnej.
Ale wiele wzorców powszechnie stosowanych do kodu przeznaczonego dla Androida jest sprzecznych z tymi stosowanymi do innego kodu Java. Nawet wiele z porad w Efektywnej Javie jest uważanych za nieodpowiednie dla Androida.
Aby osiągnąć cele zarówno idiomatycznego i przenośnego kodu, Dagger polega na ProGuard do post-processingu skompilowanego bajtkodu. To pozwala Daggerto emitować źródło, które wygląda i czuje się naturalnie zarówno na serwerze jak i na Androidzie, podczas gdy używa różnych toolchains do produkcji kodu bajtowego, który wykonuje się efektywnie w obu środowiskach. Ponadto, Dagger ma wyraźny cel, aby zapewnić, że źródło Java, które generuje jest konsekwentnie kompatybilne z optymalizacjamiProGuard.
Oczywiście, nie wszystkie problemy mogą być rozwiązane w ten sposób, ale jest to podstawowy mechanizm, dzięki któremu zostanie zapewniona kompatybilność specyficzna dla Androida.
tl;dr
Dagger zakłada, że użytkownicy na Androidzie będą używać R8 lub ProGuard.
Dlaczego Dagger na Androida jest trudny
Jedną z głównych trudności pisania aplikacji na Androida przy użyciu Dagger jest to, że wiele klas frameworka Androida jest instancjonowanych przez sam system operacyjny, jakActivity
i Fragment
, ale Dagger działa najlepiej, jeśli może tworzyć wszystkie wstrzykiwane obiekty. Zamiast tego, musisz wykonać wstrzykiwanie członków w metodzie cyklu życia. Oznacza to, że wiele klas wygląda jak:
public class FrombulationActivity extends Activity { @Inject Frombulator frombulator; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // DO THIS FIRST. Otherwise frombulator might be null! ((SomeApplicationBaseType) getContext().getApplicationContext()) .getApplicationComponent() .newActivityComponentBuilder() .activity(this) .build() .inject(this); // ... now you can write the exciting code }}
Powoduje to kilka problemów:
-
Kopiowanie kodu sprawia, że trudno jest go później refaktoryzować. Ponieważ coraz więcej programistów kopiuje ten blok, coraz mniej z nich będzie wiedziało, co on właściwie robi.
-
Ponadto, wymaga, aby typ żądający wstrzyknięcia (
FrombulationActivity
) wiedział o swoim wstrzykiwaczu. Nawet jeśli jest to zrobione poprzez interfejsy zamiast konkretnych typów, łamie to podstawową zasadę wstrzykiwania zależności: klasa nie powinna wiedzieć nic o tym, jak jest wstrzykiwana.
dagger.android
Klasy w dagger.android
oferują jedno podejście do uproszczenia powyższych problemów. Wymaga to nauczenia się kilku dodatkowych API i koncepcji, ale daje zredukowany boilerplate i wstrzykiwanie klas Androida w odpowiednim miejscu cyklu życia.
Innym podejściem jest po prostu użycie normalnych API Dagger i podążanie za przewodnikami, takimi jak ten tutaj.Może to być prostsze do zrozumienia, ale wiąże się z minusem w postaci konieczności ręcznego pisania dodatkowych szablonów.
Zespoły Jetpack i Dagger pracują razem nad nową inicjatywą dla Dagger na Androida, która ma nadzieję być dużą zmianą w stosunku do obecnego statusquo. Chociaż niestety nie jest to jeszcze gotowe, może to być coś do rozważeniaerwhen wybierając jak używać Dagger w swoich projektach Android dzisiaj.
Injecting Activity objects
-
Zainstaluj
AndroidInjectionModule
w swoim komponencie aplikacji, aby zapewnićthat wszystkie wiązania niezbędne dla tych typów bazowych są dostępne. -
Zacznij od napisania
@Subcomponent
, który implementujeAndroidInjector<YourActivity>
, z@Subcomponent.Factory
, który rozszerzaAndroidInjector.Factory<YourActivity>
:@Subcomponent(modules = ...)public interface YourActivitySubcomponent extends AndroidInjector<YourActivity> { @Subcomponent.Factory public interface Factory extends AndroidInjector.Factory<YourActivity> {}}
-
Po zdefiniowaniu podkomponentu, dodaj go do swojej hierarchii komponentów poprzez zdefiniowanie modułu, który wiąże fabrykę podkomponentu i dodanie go do komponentu, który wstrzykuje twój
Application
:@Module(subcomponents = YourActivitySubcomponent.class)abstract class YourActivityModule { @Binds @IntoMap @ClassKey(YourActivity.class) abstract AndroidInjector.Factory<?> bindYourAndroidInjectorFactory(YourActivitySubcomponent.Factory factory);}@Component(modules = {..., YourActivityModule.class})interface YourApplicationComponent { void inject(YourApplication application);}
Pro-tip: Jeśli Twój subcomponent i jego fabryka nie mają innych metod lubsupertypów poza tymi wymienionymi w kroku #2, możesz użyć
@ContributesAndroidInjector
, aby wygenerować je za Ciebie. Zamiast kroków 2 i 3, dodaj metodę modułuabstract
, która zwraca twoją aktywność, opatrz ją adnotacją@ContributesAndroidInjector
i określ moduły, które chcesz zainstalować w podkomponencie. Jeśli podkomponent potrzebuje zakresu, zastosuj adnotacje zakresu również do metody.@ActivityScope@ContributesAndroidInjector(modules = { /* modules to install into the subcomponent */ })abstract YourActivity contributeYourAndroidInjector();
-
Następnie spraw, aby Twój
Application
zaimplementowałHasAndroidInjector
i@Inject
aDispatchingAndroidInjector<Object>
toreturn z metodyandroidInjector()
:public class YourApplication extends Application implements HasAndroidInjector { @Inject DispatchingAndroidInjector<Object> dispatchingAndroidInjector; @Override public void onCreate() { super.onCreate(); DaggerYourApplicationComponent.create() .inject(this); } @Override public AndroidInjector<Object> androidInjector() { return dispatchingAndroidInjector; }}
-
Na koniec, w swojej
Activity.onCreate()
metodzie, wywołajAndroidInjection.inject(this)
przed wywołaniemsuper.onCreate();
:public class YourActivity extends Activity { public void onCreate(Bundle savedInstanceState) { AndroidInjection.inject(this); super.onCreate(savedInstanceState); }}
-
Gratulacje!
Jak to zadziałało?
AndroidInjection.inject()
otrzymuje DispatchingAndroidInjector<Object>
od Application
i przekazuje twoją aktywność do inject(Activity)
. TheDispatchingAndroidInjector
wyszukuje AndroidInjector.Factory
dla klasy twojej aktywności (którą jest YourActivitySubcomponent.Factory
), tworzy AndroidInjector
(którą jest YourActivitySubcomponent
) i przekazuje twoją aktywność do inject(YourActivity)
.
Wstrzykiwanie obiektów Fragment
Wstrzyknięcie Fragment
jest tak samo proste jak wstrzyknięcie Activity
. Zdefiniuj swój subkomponent w ten sam sposób.
Zamiast wstrzykiwać w onCreate()
, jak to się robi dla Activity
typów, wstrzykuj Fragment
s do w onAttach()
.
W przeciwieństwie do modułów zdefiniowanych dla Activity
s, masz wybór, gdzie zainstalować moduły dla Fragment
s. Możesz uczynić swój komponent Fragment
podkomponentem innego komponentu Fragment
, komponentu Activity
lub komponentuApplication
– wszystko zależy od tego, jakich innych wiązań wymaga Twój Fragment
. Po podjęciu decyzji o lokalizacji komponentu, spraw, aby odpowiadający mu typ implementował HasAndroidInjector
(jeśli jeszcze tego nie robi). Na przykład, jeśli twój Fragment
wymaga wiązań z YourActivitySubcomponent
, twój kod będzie wyglądał mniej więcej tak:
public class YourActivity extends Activity implements HasAndroidInjector { @Inject DispatchingAndroidInjector<Object> androidInjector; @Override public void onCreate(Bundle savedInstanceState) { AndroidInjection.inject(this); super.onCreate(savedInstanceState); // ... } @Override public AndroidInjector<Object> androidInjector() { return androidInjector; }}public class YourFragment extends Fragment { @Inject SomeDependency someDep; @Override public void onAttach(Activity activity) { AndroidInjection.inject(this); super.onAttach(activity); // ... }}@Subcomponent(modules = ...)public interface YourFragmentSubcomponent extends AndroidInjector<YourFragment> { @Subcomponent.Factory public interface Factory extends AndroidInjector.Factory<YourFragment> {}}@Module(subcomponents = YourFragmentSubcomponent.class)abstract class YourFragmentModule { @Binds @IntoMap @ClassKey(YourFragment.class) abstract AndroidInjector.Factory<?> bindYourFragmentInjectorFactory(YourFragmentSubcomponent.Factory factory);}@Subcomponent(modules = { YourFragmentModule.class, ... }public interface YourActivityOrYourApplicationComponent { ... }
Base Framework Types
Ponieważ DispatchingAndroidInjector
wyszukuje odpowiednieAndroidInjector.Factory
przez klasę w czasie wykonywania, klasa bazowa może zaimplementowaćHasAndroidInjector
, jak również wywołać AndroidInjection.inject()
. Wszystko, co musi zrobić każda podklasa, to powiązać odpowiednią @Subcomponent
. Dagger dostarcza kilka typów bazowych, które to robią, takich jak DaggerActivity
i DaggerFragment
, jeśli nie masz skomplikowanej hierarchii klas. Dagger dostarcza również DaggerApplication
do tego samego celu – wszystko, co musisz zrobić, to rozszerzyć go i nadpisać metodę applicationInjector()
, aby zwrócić komponent, który powinien wstrzyknąć Application
.
Zawarte są również następujące typy:
-
DaggerService
iDaggerIntentService
DaggerBroadcastReceiver
DaggerContentProvider
Uwaga: DaggerBroadcastReceiver
powinien być używany tylko wtedy, gdyBroadcastReceiver
jest zarejestrowany w AndroidManifest.xml
. GdyBroadcastReceiver
jest tworzony we własnym kodzie, preferuj konstruktor injectioninstead.
Biblioteki pomocnicze
Dla użytkowników biblioteki pomocniczej Androida, typy równoległe istnieją w pakieciedagger.android.support
.
TODO(ronshapiro): powinniśmy zacząć rozdzielać to według pakietów androidx
Jak to zdobyć?
Dodaj poniższe do swojego build.gradle:
dependencies { implementation 'com.google.dagger:dagger-android:2.x' implementation 'com.google.dagger:dagger-android-support:2.x' // if you use the support libraries annotationProcessor 'com.google.dagger:dagger-android-processor:2.x' annotationProcessor 'com.google.dagger:dagger-compiler:2.x'}
Kiedy wstrzykiwać
Wstrzykiwanie konstruktora jest preferowane, gdy tylko jest to możliwe, ponieważ javac
zapewni, że żadne pole nie jest przywoływane przed jego ustawieniem, co pomaga uniknąćNullPointerException
s. Kiedy wstrzykiwanie członków jest wymagane (jak omówiono powyżej), preferuj wstrzykiwanie tak wcześnie, jak to możliwe. Z tego powodu, DaggerActivity
wywołuje AndroidInjection.inject()
natychmiast w onCreate()
, przed wywołaniemsuper.onCreate()
, a DaggerFragment
robi to samo w onAttach()
, co również zapobiega niespójnościom, jeśli Fragment
zostanie ponownie dołączone.
Kluczowe jest wywołanie AndroidInjection.inject()
przed super.onCreate()
w Activity
, ponieważ wywołanie super
dołącza Fragment
s z poprzedniej instancji aktywności podczas zmiany konfiguracji, która z kolei wstrzykuje Fragment
s. Aby wstrzyknięcie Fragment
się powiodło, Activity
musi być już wstrzyknięte. Dla użytkowników ErrorProne, jest to błąd kompilatora, aby wywołać AndroidInjection.inject()
po super.onCreate()
.
FAQ
Scoping AndroidInjector.Factory
AndroidInjector.Factory
ma być bezstanowym interfejsem, więc implementatorzy nie muszą się martwić o zarządzanie stanem związanym z obiektem, który zostanie wstrzyknięty. Kiedy DispatchingAndroidInjector
żądaAndroidInjector.Factory
, robi to poprzez Provider
, więc nie zachowuje jawnie żadnych instancji fabryki. Ponieważ niektóre implementacje mogą zawierać instancję Activity
/Fragment
/etc, które są wstrzykiwane, błędem czasu kompilacji jest stosowanie zakresu do metod, które je udostępniają. Jeśli jesteś pewien, że twój AndroidInjector.Factory
nie zachowuje instancji wstrzykiwanego obiektu, możesz wyeliminować ten błąd przez zastosowanie@SuppressWarnings("dagger.android.ScopedInjectorFactory")
do twojej metody modułowej.