Java Virtual Machine, sau JVM pe scurt, suportă multithreading. Orice proces pe care îl executați este liber să creați un număr rezonabil de fire pentru a efectua sarcini multiple asincron. Cu toate acestea, scrierea unui cod care poate face acest lucru într-un mod optim și fără erori poate fi extrem de greu. De-a lungul timpului, Java, alte limbi JVM și o mulțime de biblioteci terțe au încercat să vină cu abordări creative și elegante pentru a rezolva această problemă.
De exemplu, Java 5 a introdus framework-ul executiv, care vă permite să decuplați detaliile managementului firului de logica dvs. de afaceri. Java 8 oferă fluxuri paralele, care pot fi utilizate cu ușurință cu expresii lambda. RxJava aduce extensii reactive la Java, permițându-vă să scrieți cod asincron foarte concis și lizibil.
Kotlin susține aproape toate aceste abordări și oferă câteva din propria sa abordare. În acest tutorial, vă vom arăta cum le puteți utiliza în aplicațiile Android.
Pentru a putea urma acest tutorial, veți avea nevoie de:
Dacă nu sunteți confortabil să lucrați cu expresii lambda și interfețe SAM, vă recomand să citiți și următorul tutorial înainte de a continua:
De asemenea, puteți afla toate intrările și ieșirile din limba Kotlin în seria Kotlin From Scratch.
De obicei, instanțe de clase care implementează runnable
sunt folosite pentru a crea fire în Kotlin. Deoarece runnable
interfața are doar o singură metodă, alerga()
, puteți utiliza caracteristica de conversie SAM a lui Kotlin pentru a crea fire noi cu un cod minimal de boilerplate.
Iată cum puteți utiliza fir()
funcția, care face parte din biblioteca standard a lui Kotlin, pentru a crea rapid și a începe un nou thread:
thread // unele operațiuni de lungă durată
Abordarea de mai sus este adecvată numai atunci când aveți nevoie să faceți ocazional un fir sau două. Dacă concurrency este o parte importantă a logicii de afaceri a aplicației dvs. și aveți nevoie de un număr mare de fire, utilizarea unei baze de thread cu un serviciu executor este o idee mai bună.
De exemplu, următorul cod utilizează newFixedThreadPool ()
metodă a executori
pentru a crea o piscină de fire conținând opt fire reutilizabile și care rulează un număr mare de operații de fundal pe ea:
val myService: ExecutorService = Executors.newFixedThreadPool (8) var i = 0 în timp ce (i < items.size) // items may be a large array val item = items[i] myService.submit processItem(item) // a long running operation i += 1
Ar putea să nu fie evident la prima vedere, dar, în codul de mai sus, argumentul pentru a depune()
metoda serviciului executor este de fapt a runnable
obiect.
Sarcini de fundal create utilizând runnable
interfața nu poate returna direct rezultatele. Dacă doriți să primiți rezultate din firele dvs., trebuie să utilizați nevărsat
interfață, care este, de asemenea, o interfață SAM.
Când treceți a nevărsat
obiecte față de a depune()
metoda unui serviciu executor, primiți un Viitor
obiect. După cum sugerează și numele, Viitor
obiect va conține rezultatul nevărsat
la un moment dat în viitor, când serviciul executorului a terminat să îl execute. Pentru a obține rezultatul real de la Viitor
obiect, tot ce trebuie să faceți este să-i numiți obține()
metodă - dar aveți grijă, firul dvs. va bloca dacă îl numiți prematură.
Următorul exemplu de cod vă arată cum să creați un nevărsat
obiect care returnează a Viitor
de tip Şir
, rulați-l și tipăriți rezultatul:
val myService: ExecutorService = Executors.newFixedThreadPool (2) val rezultat = myService.submit (Callable// o operație de fundal care generează // un șir) // Alte operațiuni // Tipăriți rezultatul Log.d (TAG, result.get ())
Spre deosebire de Java, Kotlin nu are sincronizate
cuvinte cheie. Prin urmare, pentru a sincroniza mai multe operațiuni de fundal, este de așteptat să utilizați fie @Synchronized
adnotare sau sincronizate ()
funcția inline a bibliotecii. Adnotarea poate sincroniza o întreagă metodă și funcția funcționează pe un bloc de instrucțiuni.
// functie sincronizata @ distractie sincronizata myFunction () distractie myOtherFunction () // un bloc sincronizat sincronizat (aceasta)
Amandoua @Synchronized
adnotare și sincronizate ()
utilizați conceptul de încuietori ale monitorului.
Dacă nu știți deja, fiecare obiect de pe JVM are un monitor asociat cu acesta. Deocamdată, vă puteți gândi la un monitor ca un semn special pe care un fir poate dobândi sau îl poate bloca pentru a obține acces exclusiv la obiect. Odată ce monitorul unui obiect este blocat, alte fire care vor să funcționeze pe obiect vor trebui să aștepte până când monitorul este eliberat sau deblocat din nou.
In timp ce @Synchronized
adnotarea blochează monitorul obiectului la care aparține metoda asociată, sincronizate ()
funcția poate bloca monitorul oricărui obiect care ia fost transmis ca argument.
Printr-o bibliotecă experimentală, Kotlin oferă o abordare alternativă pentru a realiza concurrency: corutine. Corutinele sunt mult mai ușoare decât firele și sunt mult mai ușor de gestionat.
În aplicațiile mobile cu mai multe fire, firele sunt de obicei folosite pentru operații cum ar fi preluarea de informații de pe Internet sau interogarea bazelor de date. Astfel de operațiuni nu implică mult calcul, iar firele petrec cea mai mare parte a vieții într-o stare blocată, așteptând doar ca datele să vină de undeva în altă parte. După cum probabil puteți spune, nu este o modalitate foarte eficientă de a utiliza CPU-ul.
Corutinele sunt concepute pentru a fi utilizate în loc de fire pentru astfel de operațiuni. Cel mai important lucru pe care trebuie să-l înțelegeți despre corutine este că acestea sunt suspendabile. Cu alte cuvinte, în loc de blocare, ei se pot opri atunci când este necesar și continuă continuu mai târziu. Acest lucru duce la o utilizare mult mai bună a procesorului. Într-adevăr, cu corutine bine concepute, puteți executa cu ușurință zeci de operații de fundal.
Pentru a putea utiliza coroutine în proiectul dvs. Android Studio, asigurați-vă că adăugați următoarele compila
dependența în aplicaţia
ale modulului build.gradle fişier:
compilați org.jetbrains.kotlinx: kotlinx-coroutines-android: 0.19.3 '
O corutină poate fi suspendată numai cu ajutorul unei funcții de suspendare. Prin urmare, cele mai multe corutine au apeluri la cel puțin o astfel de funcție în interiorul lor.
Pentru a crea o funcție de suspendare, tot ce trebuie să faceți este să adăugați suspenda
modificator la o funcție obișnuită. Iată o funcție tipică de suspendare care execută o cerere HTTP GET utilizând biblioteca khttp:
suspendați distracția fetchWebsiteContents (url: String): String returnați khttp.get (url) .text
Rețineți că o funcție de suspendare poate fi apelată numai printr-o funcție de corecție sau o altă funcție de suspendare. Dacă încercați să o apelați din orice altă parte, codul dvs. nu va reuși să compileze.
Când vine vorba de crearea unui corutin nou, biblioteca standard a lui Kotlin are destule constructori de corutină care te fac să te simți rasfatată pentru alegere. Cel mai simplu constructor de corutină pe care îl puteți utiliza este lansa()
funcția și, ca majoritatea celorlalți constructori de corutină, se așteaptă ca o lambda de suspendare, care nu este decât o funcție anonimă de suspendare. Ca atare, această lambda este ceea ce devine corutina.
Următorul cod creează o corutină care face două apeluri secvențiale la funcția de suspendare pe care am creat-o în etapa anterioară:
val job1 = lansare val site1 = fetchWebsiteContents ("https://code.tutsplus.com") val site2 = fetchWebsiteContents ("https://design.tutsplus.com")
Valoarea returnată a lansa()
funcția este a Loc de munca
obiect, pe care îl puteți folosi pentru a administra corutina. De exemplu, puteți să-i sunați a adera()
metoda de a aștepta pentru completarea corutinei. În mod similar, îi puteți numi Anulare()
metodă de anulare a corutinei.
Utilizarea lansa()
funcția este asemănătoare cu crearea unui fir nou cu a runnable
obiect, în primul rând pentru că nu puteți returna nici o valoare din acesta. Dacă doriți să puteți returna o valoare din corutină, trebuie să o creați utilizând async ()
funcția.
async ()
funcția returnează a amânat
obiect, care, la fel ca și Loc de munca
obiect, vă permite să gestionați corutina. Cu toate acestea, vă permite de asemenea să utilizați așteaptă ()
funcția de a aștepta rezultatul corutinei fără a bloca firul curent.
De exemplu, ia în considerare următoarele corutine care utilizează fetchWebsiteContents ()
suspendă funcția și returnează lungimile conținutului a două adrese de pagini web diferite:
val jobForLength1 = async fetchWebsiteContents ("https://webdesign.tutsplus.com") .length val jobForLength2 = async fetchWebsiteContents ("https://photography.tutsplus.com") .length
Cu codul de mai sus, ambele corutine vor începe imediat și se vor desfășura în paralel.
Dacă doriți acum să utilizați lungimile returnate, trebuie să apelați așteaptă ()
pe ambele amânat
obiecte. Cu toate acestea, pentru că așteaptă ()
metoda este de asemenea o funcție de suspendare, trebuie să vă asigurați că o numiți dintr-o altă cotitură.
Următorul cod vă arată cum puteți calcula suma celor două lungimi folosind un nou corutan creat cu lansa()
funcţie:
lansați val sum = jobForLength1.await () + jobForLength2.await () println ("Descărcați $ bytes sum!")
Corutinele folosesc firele de fundal pe plan intern, motiv pentru care nu rulează implicit pe firul de aplicație al aplicației Android. În consecință, dacă încercați să modificați conținutul interfeței de utilizator a aplicației din interiorul unei corutine, veți întâlni o eroare de rulare. Din fericire, este ușor să executați o corutină pe firul UI: trebuie doar să treceți UI
obiect ca argument pentru constructorul dvs. de corutină.
De exemplu, iată cum puteți rescrie ultima corutină pentru a afișa suma din interiorul a TextView
widget:
lansare (UI) val sum = jobForLength1.await () + jobForLength2.await () myTextView.text = "Descarcate suma $ bytes!"
Codul de mai sus ar putea părea lumesc la început, dar uite din nou. Nu numai că este în stare să aștepte ca două operații de fundal să se finalizeze fără a utiliza callback-uri, dar poate face acest lucru pe firul aplicației UI, fără a-l bloca!
Având abilitatea de a aștepta firul UI, fără a vă face să vă simțiți lent sau să declanșați o eroare a aplicației care nu răspunde, deseori denumită ANR, simplifică o mulțime de sarcini altfel complexe.
De exemplu, cu suspendarea întârziere()
funcția, care este echivalentul non-blocant al lui Thread.sleep ()
, puteți crea acum animații cu bucle. Pentru a vă ajuta să începeți, iată un eșantion de corutină care mărește coordonata x a TextView
widget la fiecare 400 ms, creând astfel un efect de tip marquee:
lansare (UI) în timp ce (myTextView.x < 800) myTextView.x += 10 delay(400)
În timp ce dezvoltați aplicații Android, este imperativ să efectuați operații de lungă durată în fire de fundal. În acest tutorial, ați învățat câteva abordări pe care le puteți urma pentru a crea și a gestiona astfel de fire în Kotlin. De asemenea, ați învățat cum să utilizați funcția de corutine experimentale încă pentru a aștepta în interiorul firelor, fără a le bloca.
Pentru a afla mai multe despre corutine, vă puteți referi la documentația oficială. Și în timp ce sunteți aici, verificați câteva dintre celelalte postări despre dezvoltarea companiei Kotlin și Android!