Acest articol face parte dintr-o serie de mai multe părți despre utilizarea Coroutines pe Android. Această postare se concentrează pe modul în care funcționează corutinele și pe problemele pe care le rezolvă.
Alte articole din această serie:
Corutinele Kotlin introduc un nou stil de concurrență care poate fi folosit pe Android pentru a simplifica codul asincron. Deși sunt noi în Kotlin în versiunea 1.3, conceptul de corutine a existat încă de la începuturile limbajelor de programare. Primul limbaj care a explorat utilizarea corutinelor a fost Simula în 1967.
În ultimii câțiva ani, corutinele au crescut în popularitate și sunt acum incluse în multe limbaje de programare populare, cum ar fi Javascript, C#, Python, Ruby și Go, pentru a numi doar câteva. Corutinele Kotlin se bazează pe concepte consacrate care au fost folosite pentru a construi aplicații mari.
Pe Android, corutinele sunt o soluție excelentă la două probleme:
- Sarcinile care rulează mult timp sunt sarcini care durează prea mult pentru a bloca firul principal.
- Main-safety vă permite să vă asigurați că orice funcție de suspendare poate fi apelată de pe firul principal.
Să ne scufundăm în fiecare dintre ele pentru a vedea cum corutinele ne pot ajuta să structurăm codul într-un mod mai curat!
Tașe de execuție îndelungată
Căutarea unei pagini web sau interacțiunea cu un API implică ambele efectuarea unei cereri de rețea. În mod similar, citirea dintr-o bază de date sau încărcarea unei imagini de pe disc implică citirea unui fișier. Aceste tipuri de lucruri sunt ceea ce eu numesc sarcini de lungă durată – sarcini care durează mult prea mult timp pentru ca aplicația dvs. să se oprească și să le aștepte!
Poate fi greu de înțeles cât de repede un telefon modern execută codul în comparație cu o cerere de rețea. Pe un Pixel 2, un singur ciclu CPU durează puțin sub 0,0000000004 secunde, un număr destul de greu de înțeles în termeni umani. Cu toate acestea, dacă te gândești la o solicitare de rețea ca la o clipită, în jur de 400 de milisecunde (0,4 secunde), este mai ușor să înțelegi cât de repede funcționează procesorul. Într-o clipită, sau într-o cerere de rețea oarecum lentă, procesorul poate executa peste un miliard de cicluri!
Pe Android, fiecare aplicație are un fir principal care se ocupă de gestionarea interfeței cu utilizatorul (cum ar fi desenarea vizualizărilor) și de coordonarea interacțiunilor cu utilizatorul. Dacă se lucrează prea mult pe acest fir, aplicația pare să se blocheze sau să încetinească, ceea ce duce la o experiență nedorită pentru utilizator. Orice sarcină care rulează mult timp ar trebui să fie efectuată fără a bloca firul principal, astfel încât aplicația dvs. să nu afișeze ceea ce se numește „jank”, cum ar fi animațiile înghețate, sau să răspundă lent la evenimentele de atingere.
Pentru a efectua o cerere de rețea în afara firului principal, un model comun este callback-ul. Callback-urile oferă un mâner către o bibliotecă pe care o poate folosi pentru a apela înapoi în codul dvs. la un moment dat. Cu callback-uri, preluarea developer.android.com ar putea arăta astfel:
class ViewModel: ViewModel() {
fun fetchDocs() {
get("developer.android.com") { result ->
show(result)
}
}
}
Chiar dacă get
este apelat din firul principal, acesta va folosi un alt fir pentru a efectua cererea de rețea. Apoi, odată ce rezultatul este disponibil din rețea, callback-ul va fi apelat pe firul principal. Aceasta este o modalitate foarte bună de a gestiona sarcinile de lungă durată, iar bibliotecile precum Retrofit vă pot ajuta să efectuați cereri de rețea fără a bloca firul principal.
Utilizarea corutinelor pentru sarcinile de lungă durată
Corutinele sunt o modalitate de a simplifica codul utilizat pentru a gestiona sarcinile de lungă durată precum fetchDocs
. Pentru a explora modul în care corutinele simplifică codul pentru sarcinile cu execuție îndelungată, haideți să rescriem exemplul de callback de mai sus pentru a utiliza corutine.
// Dispatchers.Main
suspend fun fetchDocs() {
// Dispatchers.Main
val result = get("developer.android.com")
// Dispatchers.Main
show(result)
}// look at this in the next section
suspend fun get(url: String) = withContext(Dispatchers.IO){/*...*/}
Nu blochează acest cod firul principal? Cum returnează un rezultat de la get
fără a aștepta cererea de rețea și fără a bloca? Se pare că corutinele oferă o modalitate pentru Kotlin de a executa acest cod și de a nu bloca niciodată firul principal.
Corutinele se bazează pe funcțiile obișnuite prin adăugarea a două noi operații. În plus față de invoke (sau call) și return, corutinele adaugă suspendarea și reluarea.
- suspend – suspendă execuția corutinei curente, salvând toate variabilele locale
- resume – continuă o corutină suspendată din locul în care a fost suspendată
Această funcționalitate este adăugată de Kotlin prin cuvântul cheie suspend din funcție. Puteți apela funcțiile de suspendare numai de la alte funcții de suspendare sau prin utilizarea unui constructor de corutine, cum ar fi launch
, pentru a începe o nouă cortină.
Suspendarea și reluarea lucrează împreună pentru a înlocui callback-urile.
În exemplul de mai sus, get
va suspenda corutina înainte de a începe solicitarea de rețea. Funcția get
va fi în continuare responsabilă pentru rularea cererii de rețea în afara firului principal. Apoi, atunci când cererea de rețea se finalizează, în loc să apeleze un callback pentru a notifica firul principal, aceasta poate pur și simplu să reia corutina pe care a suspendat-o.