Dagger și Android
Unul dintre principalele avantaje ale lui Dagger 2 față de majoritatea celorlalte framework-uri de injecție a dependențelor este că implementarea sa strict generată (fără reflexie) înseamnă că poate fi utilizată în aplicațiile Android. Cu toate acestea, există încă uneleconsiderații care trebuie luate în considerare atunci când se utilizează Dagger în cadrul aplicațiilor Android.
Filosofie
În timp ce codul scris pentru Android este sursă Java, acesta este adesea destul de diferit din punct de vedere al stilului. De obicei, astfel de diferențe există pentru a acomoda considerațiile unice de performanță ale unei platforme mobile.
Dar multe dintre modelele aplicate în mod obișnuit codului destinat Android suntcontrarii celor aplicate la alt cod Java. Chiar și o mare parte din sfaturile dinEffective Java sunt considerate nepotrivite pentru Android.
Pentru a atinge obiectivele unui cod atât idiomatic cât și portabil, Daggerse bazează pe ProGuard pentru a post-procesa codul byte compilat. Acest lucru îi permite lui Dagger să emită o sursă care arată și se simte natural atât pe server cât și pe Android,folosind în același timp diferite toolchains pentru a produce bytecode care se executăeficient în ambele medii. Mai mult, Dagger are un obiectiv explicit de a se asigura că sursa Java pe care o generează este în mod constant compatibilă cu optimizărileProGuard.
Desigur, nu toate problemele pot fi abordate în acest mod, dar acesta este principalul mecanism prin care va fi asigurată compatibilitatea specifică Android.
tl;dr
Dagger presupune că utilizatorii de pe Android vor folosi R8 sau ProGuard.
De ce Dagger pe Android este dificil
Una dintre dificultățile centrale ale scrierii unei aplicații Android folosind Dagger este că multe clase din cadrul Android sunt instanțiate de sistemul de operare însuși, cum ar fiActivity
și Fragment
, dar Dagger funcționează cel mai bine dacă poate crea toate obiectele injectate. În schimb, trebuie să efectuați injectarea membrilor într-o metodă din ciclul de viață. Acest lucru înseamnă că multe clase sfârșesc prin a arăta ca:
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 }}
Acest lucru are câteva probleme:
-
Copierea de cod face dificilă refactorizarea ulterioară. Pe măsură ce tot mai mulțidezvoltatori copiază-lipesc acel bloc, tot mai puțini vor ști ce face de fapt.
-
Mai fundamental, este nevoie ca tipul care solicită injectarea(
FrombulationActivity
) să știe despre injectorul său. Chiar dacă acest lucru se face prin interfețe în loc de tipuri concrete, se încalcă un principiu de bază al injecției de dependență: o clasă nu ar trebui să știe nimic despre modul în care este injectată.
dagger.android
Classele din dagger.android
oferă o abordare pentru a simplifica problemele de mai sus. Aceasta necesită învățarea unor API-uri și concepte suplimentare, dar vă oferă un boilerplate redus și injecție în clasele Android la locul potrivit în ciclul de viață.
O altă abordare este de a folosi doar API-urile Dagger normale și de a urma ghiduri precum cel de aici.Acest lucru poate fi mai simplu de înțeles, dar vine cu dezavantajul de a trebui să scrie manual boilerplate suplimentar.
Echipele Jetpack și Dagger lucrează împreună la o nouă inițiativăpentru Dagger pe Android care speră să fie o schimbare mare față de statusquo actual. Deși, din nefericire, nu este încă gata, acest lucru poate fi ceva de luat în considerare atunci când alegeți cum să folosiți Dagger în proiectele dvs. Android astăzi.
Injectarea obiectelor Activity
-
Instalați
AndroidInjectionModule
în componenta aplicației dvs. pentru a vă asigura că toate legăturile necesare pentru aceste tipuri de bază sunt disponibile. -
Începeți prin a scrie un
@Subcomponent
care implementeazăAndroidInjector<YourActivity>
, cu un@Subcomponent.Factory
care extindeAndroidInjector.Factory<YourActivity>
:@Subcomponent(modules = ...)public interface YourActivitySubcomponent extends AndroidInjector<YourActivity> { @Subcomponent.Factory public interface Factory extends AndroidInjector.Factory<YourActivity> {}}
-
După ce ați definit subcomponenta, adăugați-o la ierarhia componentei dvs. prin definirea unui modul care leagă fabrica subcomponentei și adăugarea acestuia la componenta care injectează
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: Dacă subcomponenta dvs. și fabrica sa nu au alte metode sau supratipuri în afară de cele menționate la pasul 2, puteți folosi
@ContributesAndroidInjector
pentru a le genera pentru dvs. În loc de pașii 2și 3, adăugați o metodă de modulabstract
care returnează activitatea dumneavoastră, adnotați-o cu@ContributesAndroidInjector
și specificați modulele pe care doriți să le instalați în subcomponentă. Dacă subcomponenta are nevoie de domenii de cuprindere, aplicați adnotările de domeniu și metodei.@ActivityScope@ContributesAndroidInjector(modules = { /* modules to install into the subcomponent */ })abstract YourActivity contributeYourAndroidInjector();
-
În continuare, faceți ca
Application
să implementezeHasAndroidInjector
și@Inject
unDispatchingAndroidInjector<Object>
să returneze din metodaandroidInjector()
: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; }}
-
În cele din urmă, în metoda dvs.
Activity.onCreate()
, apelațiAndroidInjection.inject(this)
înainte de a apelasuper.onCreate();
:public class YourActivity extends Activity { public void onCreate(Bundle savedInstanceState) { AndroidInjection.inject(this); super.onCreate(savedInstanceState); }}
-
Felicitări!
Cum a funcționat?
AndroidInjection.inject()
primește un DispatchingAndroidInjector<Object>
de la Application
și transmite activitatea ta la inject(Activity)
. DispatchingAndroidInjector
caută AndroidInjector.Factory
pentru clasa AndroidInjector.Factory
a activității tale (care este YourActivitySubcomponent.Factory
), creează AndroidInjector
(care este YourActivitySubcomponent
) și trece activitatea ta la inject(YourActivity)
.
Injectarea obiectelor Fragment
Injectarea unui Fragment
este la fel de simplă ca și injectarea unui Activity
. Definiți-vă subcomponentul în același mod.
În loc să injectați în onCreate()
, așa cum se face pentru tipurile Activity
, injectați Fragment
s la în onAttach()
.
În mod diferit de modulele definite pentru Activity
s, aveți posibilitatea de a alege unde să instalați modulele pentru Fragment
s. Puteți face din componenta Fragment
ca subcomponentă a unei alte componente Fragment
, a unei componente Activity
sau a componenteiApplication
– totul depinde de ce alte legături necesită Fragment
ul dumneavoastră. După ce v-ați hotărât asupra locației componentei, faceți ca tipul corespunzător să fie implementat HasAndroidInjector
(dacă nu o face deja). De exemplu, dacă Fragment
necesită legături de la YourActivitySubcomponent
, codul dvs. va arăta cam așa:
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 { ... }
Tipuri din cadrul de bază
Pentru că DispatchingAndroidInjector
caută la execuție AndroidInjector.Factory
corespunzătoare de către clasă, o clasă de bază poate implementaHasAndroidInjector
, precum și apela AndroidInjection.inject()
. Tot ce trebuie să facă fiecare subclasă este să lege un @Subcomponent
corespunzător. Dagger oferă câteva tipuri de bază care fac acest lucru, cum ar fi DaggerActivity
și DaggerFragment
, în cazul în care nu aveți o ierarhie complicată a claselor. Dagger oferă, de asemenea, un DaggerApplication
pentru același scop – tot ce trebuie să faceți este să îl extindeți și să suprascrieți metoda applicationInjector()
pentru a returna componenta care ar trebui să injecteze Application
.
Sunt incluse, de asemenea, următoarele tipuri:
-
DaggerService
șiDaggerIntentService
DaggerBroadcastReceiver
DaggerContentProvider
Nota: DaggerBroadcastReceiver
ar trebui să fie folosit numai atunci cândBroadcastReceiver
este înregistrat în AndroidManifest.xml
. Atunci cândBroadcastReceiver
este creat în propriul cod, preferați injectarea constructorului în locul acestuia.
Biblioteci de suport
Pentru utilizatorii bibliotecii de suport Android, tipurile paralele există în pachetuldagger.android.support
.
TODO(ronshapiro): ar trebui să începem să împărțim acest lucru pe pachete androidx
Cum îl obținem?
Adaugați următoarele la 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'}
Când să injectați
Injectarea constructorului este preferată ori de câte ori este posibil, deoarece javac
se va asiguracă niciun câmp nu este referit înainte de a fi setat, ceea ce ajută la evitareaNullPointerException
lor. Atunci când este necesară injectarea membrilor (așa cum s-a discutat mai sus), este de preferat să se injecteze cât mai devreme posibil. Din acest motiv, DaggerActivity
apela AndroidInjection.inject()
imediat în onCreate()
, înainte de a apelasuper.onCreate()
, iar DaggerFragment
face același lucru în onAttach()
, ceea ce previne, de asemenea, inconsecvențele în cazul în care Fragment
este reatașat.
Este crucial să se apeleze AndroidInjection.inject()
înainte de super.onCreate()
într-un Activity
, deoarece apelul la super
atașează Fragment
s din instanța de activitate anterioară în timpul schimbării configurației, care la rândul său injectează Fragment
s. Pentru ca injectarea Fragment
să reușească, Activity
trebuie să fie deja injectată. Pentru utilizatorii ErrorProne, este o eroare de compilator să apeleze AndroidInjection.inject()
după super.onCreate()
.
FAQ
Scoping AndroidInjector.Factory
AndroidInjector.Factory
este menită să fie o interfață fără stare, astfel încât cei care o implementează nu trebuie să se preocupe de gestionarea stării legate de obiectul care va fi injectat. Atunci când DispatchingAndroidInjector
solicită unAndroidInjector.Factory
, o face prin intermediul unui Provider
, astfel încât să nu rețină în mod explicit nicio instanță a fabricii. Deoarece unele implementări pot reține o instanță a Activity
/Fragment
/etc. care este injectat, este o eroare de compilare să se aplice un domeniu de aplicare la metodele care le furnizează. Dacă sunteți sigur că AndroidInjector.Factory
dvs. nu reține o instanță a obiectului injectat, puteți elimina această eroare aplicând@SuppressWarnings("dagger.android.ScopedInjectorFactory")
la metoda dvs. de modul.
.