Samanaikaisuus ja asynkronisuus ovat luontaisia mobiiliohjelmoinnille.
Samanaikaisuuden käsitteleminen imperatiivityylisellä ohjelmoinnilla, johon Android-ohjelmointi yleensä liittyy, voi aiheuttaa monia ongelmia. Reaktiivisen ohjelmoinnin käyttäminen RxJava , voit välttää mahdolliset samanaikaisuusongelmat tarjoamalla puhtaamman ja vähemmän virheitä aiheuttavan ratkaisun.
RxJava tarjoaa samanaikaisten, asynkronisten tehtävien yksinkertaistamisen lisäksi kyvyn suorittaa toiminnallisia tyylioperaatioita, jotka muuttavat, yhdistävät ja yhdistävät havaittavasta päästöjä, kunnes saavutamme halutun tuloksen.
Yhdistämällä RxJavan reaktiivisen paradigman ja toiminnallisen tyylin toiminnot voimme mallintaa laajan valikoiman samanaikaisrakenteita reaktiivisella tavalla myös Androidin ei-reaktiivisessa maailmassa. Tässä artikkelissa opit, miten voit tehdä juuri sen. Opit myös, miten RxJava otetaan käyttöön olemassa olevassa projektissa asteittain.
Jos olet uusi RxJava, suosittelen lukemaan viesti tässä joka puhuu joistakin RxJavan perusteista.
Yksi haasteista lisätä RxJava yhtenä kirjastoista projektiisi on, että se muuttaa perusteellisesti tapaa, jolla ajattelet koodiasi.
RxJava vaatii sinua ajattelemaan, että tietoja työnnetään pikemminkin kuin vedetään. Vaikka konsepti itsessään on yksinkertainen, vetoparadigmaan perustuvan täyden koodipohjan muuttaminen voi olla hieman pelottavaa. Vaikka johdonmukaisuus on aina ihanteellinen, sinulla ei ehkä aina ole etuoikeutta tehdä tämä siirtymä koko koodikannassasi kerralla, joten tarvitaan enemmän inkrementaalista lähestymistapaa.
Harkitse seuraavaa koodia:
/** * @return a list of users with blogs */ public List getUsersWithBlogs() { final List allUsers = UserCache.getAllUsers(); final List usersWithBlogs = new ArrayList(); for (User user : allUsers) { if (user.blog != null && !user.blog.isEmpty()) { usersWithBlogs.add(user); } } Collections.sort(usersWithBlogs, (user1, user2) -> user1.name.compareTo(user2.name)); return usersWithBlogs; }
Tämä toiminto saa luettelon User
välimuistin objektit, suodattaa ne kaikki sen perusteella, onko käyttäjällä blogia, lajittelee ne käyttäjän nimen mukaan ja palauttaa ne lopuksi soittajalle. Tarkasteltaessa tätä katkelmaa huomaamme, että monet näistä toiminnoista voivat hyödyntää RxJava-operaattoreita; esim. filter()
ja sorted()
.
Tämän katkelman uudelleenkirjoittaminen antaa meille:
/** * @return a list of users with blogs */ public Observable getUsersWithBlogs() { return Observable.fromIterable(UserCache.getAllUsers()) .filter(user -> user.blog != null && !user.blog.isEmpty()) .sorted((user1, user2) -> user1.name.compareTo(user2.name)); }
Funktion ensimmäinen rivi muuntaa List
palauttanut UserCache.getAllUsers()
kohtaan Observable
kautta fromIterable()
. Tämä on ensimmäinen askel koodin muuttamiseksi reaktiiviseksi. Nyt kun toimimme Observable
: lla, tämä antaa meille mahdollisuuden suorittaa kaikki Observable
operaattori RxJava-työkalupakissa - filter()
ja sorted()
tässä tapauksessa.
Tästä muutoksesta on syytä huomata muutama muu asia.
Ensinnäkin menetelmän allekirjoitus ei ole enää sama. Tämä ei ehkä ole valtava juttu, jos tätä menetelmäkutsu käytetään vain muutamassa paikassa ja muutokset on helppo levittää pinon muille alueille; kuitenkin, jos se rikkoo asiakkaita, jotka luottavat tähän menetelmään, se on ongelmallista ja menetelmän allekirjoitus tulisi palauttaa.
Toiseksi RxJava on suunniteltu laiskuutta ajatellen. Toisin sanoen ei pidä suorittaa pitkiä operaatioita, kun Observable
: n tilaajia ei ole. Tällä muunnoksella tämä oletus ei ole enää totta, koska UserCache.getAllUsers()
kutsutaan jo ennen kuin tilaajia on.
Ensimmäisen muutoksemme ongelman ratkaisemiseksi voimme käyttää mitä tahansa esto-operaattoria Observable
: n käytettävissä kuten blockingFirst()
ja blockingNext()
. Pohjimmiltaan molemmat operaattorit estävät, kunnes kohde lähetetään alavirtaan: blockingFirst()
palauttaa ensimmäisen lähettämänsä elementin ja viimeistelee, kun taas blockingNext()
palauttaa Iterable
jonka avulla voit suorittaa jokaiselle silmukalle taustalla oleville tiedoille (jokainen iterointi silmukan läpi estää).
Sivuvaikutus estotoiminnon käytöstä, joka on kuitenkin tärkeää olla tietoinen, on se, että poikkeukset heitetään kutsuketjuun sen sijaan, että ne välitettäisiin tarkkailijan onError()
menetelmä.
Vaihtaessasi esto-operaattoria metodin allekirjoituksen palauttamiseksi List
: ksi, katkelmamme näyttäisi nyt tältä:
/** * @return a list of users with blogs */ public List getUsersWithBlogs() { return Observable.fromIterable(UserCache.getAllUsers()) .filter(user -> user.blog != null && !user.blog.isEmpty()) .sorted((user1, user2) -> user1.name.compareTo(user2.name)) .toList() .blockingGet(); }
Ennen kuin soitamme esto-operaattorille (ts. blockingGet()
), Meidän on ensin ketjutettava aggregaattioperaattori toList()
siten, että streamia muokataan Observable
-kohdasta kohtaan Single
(a Single
on erityistyyppi Observable
joka tuottaa vain yhden arvon onSuccess()
: ssa tai virheen onError()
: n kautta).
Jälkeenpäin voimme soittaa esto-operaattorille blockingGet()
joka avaa Single
ja palauttaa List
.
Vaikka RxJava tukee tätä, tätä tulisi mahdollisimman paljon välttää, koska tämä ei ole idiomaattinen reaktiivinen ohjelmointi. Aina, kun se on ehdottoman välttämätöntä, operaattoreiden estäminen on hieno ensimmäinen tapa poistua reaktiivisesta maailmasta.
Kuten aiemmin mainittiin, RxJava on suunniteltu laiskuutta ajatellen. Toisin sanoen pitkäkestoisia toimintoja tulisi viivästyttää niin kauan kuin mahdollista (ts. Kunnes tilaus kutsutaan Observable
). Jotta ratkaisumme olisi laiska, käytämme defer()
-merkkiä operaattori.
defer()
vie ObservableSource
tehdas, joka luo Observable
jokaiselle uudelle tilaajalle. Meidän tapauksessamme haluamme palata Observable.fromIterable(UserCache.getAllUser())
aina kun tarkkailija tilaa.
/** * @return a list of users with blogs */ public Observable getUsersWithBlogs() { return Observable.defer(() -> Observable.fromIterable(UserCache.getAllUsers())) .filter(user -> user.blog != null && !user.blog.isEmpty()) .sorted((user1, user2) -> user1.name.compareTo(user2.name)); }
Nyt kun pitkäaikainen operaatio on kääritty defer()
: een, meillä on täysi hallinta mitä säiettä tämän pitäisi suorittaa yksinkertaisesti määrittämällä sopiva Scheduler
sisään subscribeOn()
Tämän muutoksen myötä koodimme on täysin reaktiivinen, ja tilauksen tulisi tapahtua vain silloin, kun tietoja tarvitaan.
/** * @return a list of users with blogs */ public Observable getUsersWithBlogs() { return Observable.defer(() -> Observable.fromIterable(UserCache.getAllUsers())) .filter(user -> user.blog != null && !user.blog.isEmpty()) .sorted((user1, user2) -> user1.name.compareTo(user2.name)) .subscribeOn(Schedulers.io()); }
Toinen varsin hyödyllinen operaattori laskennan lykkäämisessä on fromCallable()
menetelmä. Toisin kuin defer()
, joka odottaa Observable
palautetaan lambda-toiminnolla ja puolestaan 'tasoittaa' palautetun Observable
, fromCallable()
vetoaa lambdaan ja palauttaa arvon alavirtaan.
/** * @return a list of users with blogs */ public Observable getUsersWithBlogs() { final Observable usersObservable = Observable.fromCallable(() -> UserCache.getAllUsers()); final Observable userObservable = usersObservable.flatMap(users -> Observable.fromIterable(users)); return userObservable.filter(user -> user.blog != null && !user.blog.isEmpty()) .sorted((user1, user2) -> user1.name.compareTo(user2.name)); }
Yksittäinen käyttö fromCallable()
luettelossa palauttaisi nyt Observable
, meidän on tasoitettava tämä luettelo käyttämällä flatMap()
.
Aikaisemmista esimerkeistä olemme nähneet, että voimme kääriä minkä tahansa objektin Observable
ja hyppää ei-reaktiivisten ja reaktiivisten tilojen välillä estotoiminnoilla ja defer()
/ fromCallable()
. Näiden rakenteiden avulla voimme alkaa muuntaa Android-sovelluksen alueet reaktiivisiksi.
Hyvä paikka aluksi ajatella RxJavan käyttöä on aina, kun sinulla on jonkin aikaa vievä prosessi, kuten verkkopuhelut (tarkista edellinen viesti levy esimerkiksi lukee ja kirjoittaa jne. Seuraava esimerkki kuvaa yksinkertaisen toiminnon, joka kirjoittaa tekstiä tiedostojärjestelmään:
/** * Writes {@code text} to the file system. * * @param context a Context * @param filename the name of the file * @param text the text to write * @return true if the text was successfully written, otherwise, false */ public boolean writeTextToFile(Context context, String filename, String text) { FileOutputStream outputStream; try { outputStream = context.openFileOutput(filename, Context.MODE_PRIVATE); outputStream.write(text.getBytes()); outputStream.close(); return true; } catch (Exception e) { e.printStackTrace(); return false; } }
Kun soitat tätä toimintoa, meidän on varmistettava, että se tehdään erillisellä säikeellä, koska tämä toiminto estää. Tällaisen rajoituksen asettaminen soittajalle vaikeuttaa kehittäjää, mikä lisää virheiden todennäköisyyttä ja voi hidastaa kehitystä.
Kommentin lisääminen toimintoon auttaa tietysti välttämään soittajan virheitä, mutta se on silti kaukana luodinkestävästä.
RxJavaa käyttämällä voimme kuitenkin helposti käärittää tämän Observable
: ksi ja määritä Scheduler
että sen pitäisi jatkua. Tällä tavalla soittajan ei tarvitse olla huolissaan toiminnon kutsumisesta erilliseen säikeeseen; toiminto hoitaa tämän itse.
/** * Writes {@code text} to the filesystem. * * @param context a Context * @param filename the name of the file * @param text the text to write * @return An Observable emitting a boolean indicating whether or not the text was successfully written. */ public Observable writeTextToFile(Context context, String filename, String text) { return Observable.fromCallable(() -> { FileOutputStream outputStream; outputStream = context.openFileOutput(filename, Context.MODE_PRIVATE); outputStream.write(text.getBytes()); outputStream.close(); return true; }).subscribeOn(Schedulers.io()); }
Käyttämällä fromCallable()
-painiketta tekstin kirjoittaminen tiedostoon lykätään tilausaikaan saakka.
Koska poikkeukset ovat RxJavan ensiluokkaisia objekteja, yksi muutoksemme etu on, että meidän ei enää tarvitse kääriä operaatiota try / catch-lohkoon. Poikkeus yksinkertaisesti levitetään alavirtaan eikä niellä. Tämä antaa soittajalle mahdollisuuden käsitellä hänen mielestään sopivaa poikkeusta (esim. Näyttää virheen käyttäjälle heitetyn poikkeuksen mukaan jne.).
Yksi optimointi, jonka voimme suorittaa, on palauttaa Completable
pikemminkin kuin Observable
A Completable
on pohjimmiltaan erityistyyppi Observable
- samanlainen kuin Single
- se yksinkertaisesti osoittaa, onnistuiko laskenta onComplete()
- kautta tai epäonnistui onError()
Palautetaan Completable
näyttää olevan järkevämpää tässä tapauksessa, koska tuntuu typerältä palauttaa yksittäinen tosi Observable
virta.
/** * Writes {@code text} to the filesystem. * * @param context a context * @param filename the name of the file * @param text the text to write * @return A Completable */ public Completable writeTextToFile(Context context, String filename, String text) { return Completable.fromAction(() -> { FileOutputStream outputStream; outputStream = context.openFileOutput(filename, Context.MODE_PRIVATE); outputStream.write(text.getBytes()); outputStream.close(); }).subscribeOn(Schedulers.io()); }
Toiminnon suorittamiseksi käytämme fromAction()
Completable
koska paluuarvo ei enää kiinnosta meitä. Tarvittaessa, kuten Observable
, a Completable
tukee myös fromCallable()
ja defer()
toimintoja.
Toistaiseksi kaikki tarkastelemamme esimerkit lähettävät joko yhtä arvoa (ts. Voidaan mallintaa Single
) tai kertovat meille, onnistuiko toiminto tai epäonnistui (ts. Voidaan mallintaa Completable
).
Kuinka voimme kuitenkin muuntaa sovelluksemme alueita, jotka saavat jatkuvasti päivityksiä tai tapahtumia (kuten sijaintipäivityksiä, napsautustapahtumien, anturitapahtumien jne.)?
Etsimme kahta tapaa tehdä tämä käyttämällä create()
ja käyttämällä Subjects
.
create()
avulla voimme nimenomaisesti kutsua tarkkailijan onNext()
| onComplete()
| onError()
menetelmä, kun saamme päivityksiä tietolähteeltämme. Jos haluat käyttää create()
, siirrymme sisään ObservableOnSubscribe
joka saa ObservableEmitter
aina kun tarkkailija tilaa. Vastaanotetun lähettimen avulla voimme sitten suorittaa kaikki tarvittavat asennuspuhelut aloittaaksemme päivitykset ja kutsumalla sitten sopivat Emitter
tapahtuma.
Sijaintipäivitysten tapauksessa voimme rekisteröityä vastaanottamaan päivityksiä tähän paikkaan ja lähettämään sijaintipäivityksiä vastaanotettuina.
public class LocationManager { /** * Call to receive device location updates. * @return An Observable emitting location updates */ public Observable observeLocation() { return Observable.create(emitter -> { // Make sure that the following conditions apply and if not, call the emitter's onError() method // (1) googleApiClient is connected // (2) location permission is granted final LocationRequest locationRequest = new LocationRequest(); locationRequest.setInterval(1000); locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); LocationServices.FusedLocationApi.requestLocationUpdates(googleApiClient, locationRequest, new LocationListener() { @Override public void onLocationChanged(Location location) { if (!emitter.isDisposed()) { emitter.onNext(location); } } }); }); } }
Toiminto create()
: n sisällä puhelu pyytää sijaintipäivityksiä ja välittää soittopyynnön, joka käynnistetään, kun laitteen sijainti muuttuu. Kuten näemme täällä, korvaamme olennaisesti takaisinsoittotyyppisen käyttöliittymän ja lähetämme sen sijaan vastaanotetun sijainnin luodussa Observable-virrassa (koulutustarkoituksiin ohitin osan yksityiskohdista rakentamalla sijaintipyynnön, jos haluat kaivaa syvemmälle yksityiskohtiin voit lukea sen tässä ).
Yksi muu huomioitava asia create()
on se aina subscribe()
kutsutaan, tarjotaan uusi säteilijä. Toisin sanoen create()
palaa kylmä Observable
. Tämä tarkoittaa, että yllä olevassa toiminnossa pyysimme mahdollisesti päivityksiä useita kertoja, mitä emme halua.
Tämän kiertämiseksi haluamme muuttaa toiminnon palauttamaan kuuma Observable
avulla Subjects
.
A Subject
ulottuu Observable
ja työkalut Observer
samaan aikaan. Tämä on erityisen hyödyllistä, kun haluamme lähettää tai lähettää saman tapahtuman useille tilaajille samanaikaisesti. Toteutuksen kannalta haluamme paljastaa Subject
kuin Observable
asiakkaille pitäen sitä Subject
palveluntarjoajalle.
public class LocationManager { private Subject locationSubject = PublishSubject.create(); /** * Invoke this method when this LocationManager should start listening to location updates. */ public void connect() { final LocationRequest locationRequest = new LocationRequest(); locationRequest.setInterval(1000); locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); LocationServices.FusedLocationApi.requestLocationUpdates(googleApiClient, locationRequest, new LocationListener() { @Override public void onLocationChanged(Location location) { locationSubject.onNext(location); } }); } /** * Call to receive device location updates. * @return An Observable emitting location updates */ public Observable observeLocation() { return locationSubject; } }
Tässä uudessa toteutuksessa alatyyppi PublishSubject
käytetään, joka lähettää tapahtumia saapuessaan tilaushetkestä alkaen. Vastaavasti, jos tilaus tehdään paikassa, jossa sijaintipäivitykset on jo lähetetty, tarkkailija ei vastaanota aikaisempia päästöjä, vain myöhemmät. Jos tätä ei haluta, on olemassa muutama muu Subject
RxJava-työkalupakin alatyypit, jotka voivat olla käytetty .
Lisäksi loimme myös erillisen connect()
toiminto, joka käynnistää pyynnön saada sijaintipäivityksiä. observeLocation()
voi silti tehdä connect()
puhelun, mutta muutimme sen pois toiminnosta selkeyden / yksinkertaisuuden vuoksi.
Olemme tarkastelleet useita mekanismeja ja tekniikoita:
defer()
ja sen variantit viivästyttävät laskennan suorittamista liittymiseen saakkaObservables
tuotettu create()
Observables
käyttämällä Subjects
Toivottavasti tämän artikkelin esimerkit inspiroivat ideoita sovelluksesi eri alueista, jotka voidaan muuntaa reaktiivisiksi. Olemme käsitelleet paljon ja jos sinulla on kysyttävää, ehdotuksia tai jos jotain ei ole selvää, voit jättää kommentin alla!
Jos olet kiinnostunut oppimaan lisää RxJavasta, työskentelen perusteellisen kirjan kanssa, joka selittää kuinka nähdä ongelmat reaktiivisella tavalla Android-esimerkkien avulla. Jos haluat saada päivityksiä siitä, tilaa tässä .