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:

  1. Koodin kopioiminen vaikeuttaa refaktorointia myöhemmin. Kun yhä useammatkehittäjät kopioivat tuon lohkon, yhä harvempi tietää, mitä se oikeastaan tekee.

  2. 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

  1. Asenna AndroidInjectionModule sovelluskomponenttiisi varmistaaksesi, että kaikki näiden perustyyppien tarvitsemat sidonnat ovat käytettävissä.

  2. 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> {}}
  3. 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();
  4. Seuraavaksi tee Application toteutuksen HasAndroidInjectorja @Inject aDispatchingAndroidInjector<Object> palaa androidInjector() 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; }}
  5. Viimeiseksi, kutsu Activity.onCreate()-metodissasi AndroidInjection.inject(this)ennen kuin kutsut super.onCreate();:

    public class YourActivity extends Activity { public void onCreate(Bundle savedInstanceState) { AndroidInjection.inject(this); super.onCreate(savedInstanceState); }}
  6. 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 Activitytyypeille 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 Fragmenttarvitsee 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 ja DaggerIntentService
  • 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äänNullPointerExceptions. Kun jäsenten injektio on tarpeen (kuten edellä käsiteltiin), injektoidaan mieluummin mahdollisimman aikaisin. Tästä syystä DaggerActivitykutsuu 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.