Dagger ja Android
Yksi Dagger 2:n tärkeimmistä eduista useimpiin muihin riippuvuusinjektiokehyksiin verrattuna on se, että sen tiukasti luotu toteutus (ei heijastusta) tarkoittaa, että sitä voidaan käyttää Android-sovelluksissa. Daggeria Android-sovelluksissa käytettäessä on kuitenkin vielä otettava huomioon joitain seikkoja.
Filosofia
Androidille kirjoitettu koodi on Java-lähdekoodia, mutta se on usein tyyliltään hyvin erilaista. Tyypillisesti tällaiset erot ovat olemassa, jotta voidaan ottaa huomioon mobiilialustan ainutlaatuisetsuorituskykyyn liittyvät näkökohdat.
Mutta monet Androidille tarkoitettuun koodiin yleisesti sovellettavista malleista ovat ristiriidassa muuhun Java-koodiin sovellettavien mallien kanssa. Jopa suuri osa Efficive Java -kirjassa annetuista neuvoista katsotaan sopimattomiksi Androidille.
Edullisen ja siirrettävän koodin tavoitteiden saavuttamiseksi Dagger tukeutuu ProGuardiin käännetyn tavukoodin jälkikäsittelyssä. Näin Daggerto voi tuottaa lähdekoodia, joka näyttää ja tuntuu luonnolliselta sekä palvelimella että Androidilla, ja käyttää samalla eri työkaluketjuja tuottamaan tavukoodia, joka suoritetaan tehokkaasti molemmissa ympäristöissä. Lisäksi Daggerilla on nimenomainen tavoite varmistaa, että sen tuottama Java-lähdekoodi on johdonmukaisesti yhteensopivaProGuard-optimointien kanssa.
Kaikkea ongelmaa ei tietenkään voida ratkaista tällä tavalla, mutta se on ensisijainen mekanismi, jolla Android-kohtainen yhteensopivuus saavutetaan.
tl;dr
Dagger olettaa, että Android-käyttäjät käyttävät R8:a tai ProGuardia.
Miksi Dagger Androidissa on vaikeaa
Yksi keskeisistä vaikeuksista Android-sovelluksen kirjoittamisessa Daggerilla on se, että monet Android-kehyksen luokat instanttisoidaan käyttöjärjestelmän itsensä toimesta, kutenActivity
ja Fragment
, mutta Dagger toimii parhaiten, jos se voi luoda kaikki injektoidut objektit. Sen sijaan sinun on suoritettava jäsenten injektointi elinkaarimenetelmässä. Tämä tarkoittaa, että monet luokat päätyvät näyttämään seuraavanlaisilta:
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 }}
Tässä on muutamia ongelmia:
-
Koodin kopioiminen vaikeuttaa refaktorointia myöhemmin. Kun yhä useammatkehittäjät kopioivat tuon lohkon, yhä harvempi tietää, mitä se oikeastaan tekee.
-
Periaatteellisempaa on, että se vaatii injektiota pyytävän tyypin(
FrombulationActivity
) tietävän injektorinsa. Vaikka tämä tehdäänkin rajapintojen kautta konkreettisten tyyppien sijaan, se rikkoo riippuvuusinjektion ydinperiaatetta: luokan ei pitäisi tietää mitään siitä, miten se injektoidaan.
dagger.android
Kohdassa dagger.android
olevat luokat tarjoavat yhden lähestymistavan yllämainittujen ongelmien yksinkertaistamiseen. Tämä vaatii joidenkin ylimääräisten API:iden ja käsitteiden opettelua, mutta antaa sinulle vähennettyä boilerplatea ja injektiota Android-luokissasi oikeassa paikassa elinkaaressa.
Toinen lähestymistapa on vain käyttää normaaleja Daggerin API:ita ja seurata oppaita, kuten tässä olevaa.Tämä voi olla yksinkertaisempaa ymmärtää, mutta sen haittapuolena on ylimääräisen boilerplaten kirjoittaminen manuaalisesti.
Jetpack- ja Dagger-tiimit työskentelevät yhdessä uuden aloitteen parissa Daggeria varten Androidissa, jonka toivotaan olevan suuri muutos nykyisestä statusquosta. Vaikka se ei valitettavasti ole vielä valmis, tämä voi olla jotakin harkittavaa, kun valitset, miten käytät Daggeria Android-projekteissasi tänään.
Activity-objektien injektointi
-
Asenna
AndroidInjectionModule
sovelluskomponenttiisi varmistaaksesi, että kaikki näiden perustyyppien tarvitsemat sidonnat ovat käytettävissä. -
Aloita kirjoittamalla
@Subcomponent
, joka toteuttaaAndroidInjector<YourActivity>
, ja@Subcomponent.Factory
, joka laajentaaAndroidInjector.Factory<YourActivity>
:@Subcomponent(modules = ...)public interface YourActivitySubcomponent extends AndroidInjector<YourActivity> { @Subcomponent.Factory public interface Factory extends AndroidInjector.Factory<YourActivity> {}}
-
Kun olet määritellyt alakomponentin, lisää se komponenttihierarkiaasi määrittelemällä moduuli, joka sitoo alakomponenttitehtaan, ja lisäämällä sen siihen komponenttiin, joka injektoi
Application
:si:@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-vinkki: Jos alikomponentillasi ja sen tehtaalla ei ole muita metodeja tai ylityyppejä kuin ne, jotka on mainittu vaiheessa #2, voit käyttää
@ContributesAndroidInjector
:a luomaan ne puolestasi. Vaiheiden 2 ja 3 sijasta lisääabstract
-moduulimetodi, joka palauttaa aktiviteettisi, merkitse se@ContributesAndroidInjector
:lla ja määritä moduulit, jotka haluat asentaa alakomponenttiin. Jos alakomponentti tarvitsee laajuuksia, kiinnitä metodiin myös laajuusmerkinnät.@ActivityScope@ContributesAndroidInjector(modules = { /* modules to install into the subcomponent */ })abstract YourActivity contributeYourAndroidInjector();
-
Seuraavaksi tee
Application
toteutuksenHasAndroidInjector
ja@Inject
aDispatchingAndroidInjector<Object>
palaaandroidInjector()
metodista: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; }}
-
Viimeiseksi, kutsu
Activity.onCreate()
-metodissasiAndroidInjection.inject(this)
ennen kuin kutsutsuper.onCreate();
:public class YourActivity extends Activity { public void onCreate(Bundle savedInstanceState) { AndroidInjection.inject(this); super.onCreate(savedInstanceState); }}
-
Onnittelut!
Miten se onnistui?
AndroidInjection.inject()
saa :n Application
:ltä ja välittää aktiviteettisi inject(Activity)
:lle. DispatchingAndroidInjector
etsii AndroidInjector.Factory
:stä aktiviteettisi luokan (joka on YourActivitySubcomponent.Factory
), luo AndroidInjector
:n (joka on YourActivitySubcomponent
) ja välittää aktiviteettisi inject(YourActivity)
:lle.
Fragmenttiobjektien injektointi
Injektointi Fragment
:llä on yhtä yksinkertaista kuin injektointi Activity
:llä. Määrittele alikomponenttisi samalla tavalla.
Sen sijaan, että injektoisit onCreate()
:iin, kuten Activity
tyypeille tehdään, injektoi Fragment
:t onAttach()
:iin.
Toisin kuin Activity
:ille määritellyt moduulit, sinulla on valinnanvaraa sen suhteen, minne asennat moduulit Fragment
:ille. Voit tehdä Fragment
-komponentistasi toisen Fragment
-komponentin, Activity
-komponentin tai Application
-komponentin subkomponentin – kaikki riippuu siitä, mitä muita sidoksia Fragment
:si vaatii. Kun olet päättänyt komponentin sijainnin, tee vastaavan tyypin toteutus HasAndroidInjector
(jos se ei jo ole). Jos esimerkiksi Fragment
tarvitsee sidoksia YourActivitySubcomponent
:sta, koodisi näyttää jotakuinkin tältä:
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
Koska DispatchingAndroidInjector
hakee ajonaikana luokasta sopivanAndroidInjector.Factory
, perusluokka voi toteuttaaHasAndroidInjector
sekä kutsua AndroidInjection.inject()
. Kunkin alaluokan tarvitsee vain sitoa vastaava @Subcomponent
. Dagger tarjoaa muutamia perustyyppejä, jotka tekevät tämän, kuten DaggerActivity
ja DaggerFragment
,jos sinulla ei ole monimutkaista luokkahierarkiaa. Dagger tarjoaa myös DaggerApplication
-tyypin samaan tarkoitukseen – sinun tarvitsee vain laajentaa sitä ja ohittaa applicationInjector()
-metodi palauttaaksesi komponentin, jonka pitäisi injektoida Application
.
Seuraavat tyypit ovat myös mukana:
-
DaggerService
jaDaggerIntentService
DaggerBroadcastReceiver
DaggerContentProvider
Huomautus: DaggerBroadcastReceiver
:tä tulisi käyttää vain silloin, kun BroadcastReceiver
on rekisteröity AndroidManifest.xml
. KunBroadcastReceiver
luodaan omassa koodissa, suosi sen sijaan konstruktori-injektiota.
Tukikirjastot
Androidin tukikirjaston käyttäjille rinnakkaistyypit ovat olemassadagger.android.support
-paketissa.
TODO(ronshapiro): tätä pitäisi alkaa jakaa androidx-pakettien mukaan
Miten saan sen?
Lisää 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'}
Milloin injektoida
Konstruktorin injektointia suositaan aina, kun se on mahdollista, koska javac
se varmistaa, ettei mihinkään kenttään viitata ennen kuin se on asetettu, mikä auttaa välttämäänNullPointerException
s. Kun jäsenten injektio on tarpeen (kuten edellä käsiteltiin), injektoidaan mieluummin mahdollisimman aikaisin. Tästä syystä DaggerActivity
kutsuu AndroidInjection.inject()
välittömästi onCreate()
:ssä ennensuper.onCreate()
:n kutsumista, ja DaggerFragment
tekee saman onAttach()
:ssä, mikä myös estää epäjohdonmukaisuudet, jos Fragment
liitetään uudelleen.
On ratkaisevan tärkeää kutsua AndroidInjection.inject()
ennen super.onCreate()
:aa Activity
:ssä, koska kutsu super
:iin kiinnittää Fragment
:t edellisestä aktiviteetti-instanssista konfiguraatiomuutoksen aikana, mikä puolestaan injektoiFragment
:t. Jotta Fragment
:n injektointi onnistuisi, Activity
:n on oltava jo injektoitu. ErrorPronen käyttäjille on kääntäjävirhe kutsua AndroidInjection.inject()
super.onCreate()
:n jälkeen.
FAQ
Scoping AndroidInjector.Factory
AndroidInjector.Factory
on tarkoitettu tilattomaksi rajapinnaksi, jotta toteuttajien ei tarvitse huolehtia injektoitavaan objektiin liittyvän tilan hallinnasta. Kun DispatchingAndroidInjector
pyytää AndroidInjector.Factory
:n, se tekee sen Provider
:n kautta, jotta se ei eksplisiittisesti säilytä mitään tehtaan instansseja. Koska jotkin toteutukset saattavat säilyttää injektoitavan Activity
/Fragment
/etc:n instanssin, on käännösaikainen virhe soveltaa scopea metodeihin, jotka tarjoavat niitä. Jos olet varma, että AndroidInjector.Factory
-menetelmäsi ei säilytä instanssia injektoitavaan objektiin, voit estää tämän virheen soveltamalla@SuppressWarnings("dagger.android.ScopedInjectorFactory")
moduulimetodiin.