Corutine Kotlin pe Android
O corutină este un model de proiectare a concurenței pe care îl puteți utiliza pe Android pentru a simplifica codul care se execută în mod asincron.Corutinele au fost adăugate la Kotlin în versiunea 1.3 și se bazează pe concepte consacrate din alte limbaje.
Pe Android, corutinele ajută la gestionarea sarcinilor de lungă durată care, în caz contrar, ar putea bloca firul principal și ar putea face ca aplicația dumneavoastră să nu mai răspundă.Peste 50% dintre dezvoltatorii profesioniști care folosesc corutine au raportat că au observat o productivitate crescută.Acest subiect descrie modul în care puteți folosi corutinele Kotlin pentru a rezolva acesteprobleme, permițându-vă să scrieți un cod de aplicație mai curat și mai concis.
Caracteristici
Corutine este soluția noastră recomandată pentru programarea asincronă peAndroid. Caracteristicile notabile includ următoarele:
- Ușor: Puteți rula mai multe corutine pe un singur fir de execuție datorită suportului pentrususpensie, care nu blochează firul de execuție în care se execută corutina. Suspendarea economisește memorie în comparație cu blocarea, suportând în același timp multe operații simultane.
- Mai puține scurgeri de memorie: Folosește concurența structurată pentru a rula operații în cadrul unui domeniu de aplicare.
- Suport încorporat pentru anulare: Anularea este propagată automat prin ierarhia corutinei în execuție.
- Integrare Jetpack: Multe biblioteci Jetpack includextensiuni care oferă suport complet pentru corutine. Unelebiblioteci oferă, de asemenea, propria lor sferă de corutine pe care o puteți utiliza pentru concurență structurată.
Exemple de prezentare generală
Bazate pe Ghidul de arhitectură a aplicațiilor, exemplele din acest subiect fac o cerere de rețea și returnează rezultatul la firul principal, unde aplicația poate afișa apoi rezultatul către utilizator.
În mod specific, componenta ViewModel
Arhitectură apelează stratul de depozit pe firul principal pentru a declanșa cererea de rețea. Acest ghid trece prin diverse soluții care utilizează corutinele mențin deblocat firul principal.
ViewModel
include un set de extensii KTX care lucrează direct cu corutinele. Aceste extensii suntlifecycle-viewmodel-ktx
bibliotecă și sunt utilizateîn acest ghid.
Informații despre dependență
Pentru a utiliza coroutine în proiectul dvs. Android, adăugați următoarea dependență la fișierul aplicației dvs. build.gradle
:
dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'}
Executarea într-un fir de fundal
Facerea unei cereri de rețea pe firul principal face ca acesta să aștepte, sau să se blocheze,până când primește un răspuns. Deoarece firul de execuție este blocat, sistemul de operare nu trebuie să apeleze onDraw()
, ceea ce face ca aplicația dvs. să se blocheze și poate duce la un dialog ANR (Application Not Responding). Pentru o mai bună experiență de utilizare, să rulăm această operațiune pe un fir de execuție în fundal.
În primul rând, să aruncăm o privire la clasa noastră Repository
și să vedem cum face cererea de rețea:
sealed class Result<out R> { data class Success<out T>(val data: T) : Result<T>() data class Error(val exception: Exception) : Result<Nothing>()}class LoginRepository(private val responseParser: LoginResponseParser) { private const val loginUrl = "https://example.com/login" // Function that makes the network request, blocking the current thread fun makeLoginRequest( jsonBody: String ): Result<LoginResponse> { val url = URL(loginUrl) (url.openConnection() as? HttpURLConnection)?.run { requestMethod = "POST" setRequestProperty("Content-Type", "application/json; utf-8") setRequestProperty("Accept", "application/json") doOutput = true outputStream.write(jsonBody.toByteArray()) return Result.Success(responseParser.parse(inputStream)) } return Result.Error(Exception("Cannot open HttpURLConnection")) }}
makeLoginRequest
este sincronă și blochează firul apelant. Pentru a modela răspunsul la cererea de rețea, avem propria noastră clasă Result
.
Clasa ViewModel
declanșează cererea de rețea atunci când utilizatorul face clic, de exemplu, pe un buton:
class LoginViewModel( private val loginRepository: LoginRepository): ViewModel() { fun login(username: String, token: String) { val jsonBody = "{ username: \"$username\", token: \"$token\"}" loginRepository.makeLoginRequest(jsonBody) }}
Cu codul anterior, LoginViewModel
blochează firul UI atunci când face cererea de rețea. Cea mai simplă soluție pentru a muta execuția de pe firul principal este de a crea o nouă cortină și de a executa cererea de rețea pe un fir de intrare/ieșire:
class LoginViewModel( private val loginRepository: LoginRepository): ViewModel() { fun login(username: String, token: String) { // Create a new coroutine to move the execution off the UI thread viewModelScope.launch(Dispatchers.IO) { val jsonBody = "{ username: \"$username\", token: \"$token\"}" loginRepository.makeLoginRequest(jsonBody) } }}
Să disecăm codul cortinei din funcția login
:
-
viewModelScope
viewModelScope
este oCoroutineScope
predefinită care este inclusă cu extensiileViewModel
KTX. Rețineți că toate corutinele trebuie să ruleze în ascope. UnCoroutineScope
gestionează una sau mai multe corutine înrudite. -
launch
este o funcție care creează o corutină și distribuie execuția corpului funcției sale către dispeceratul corespunzător. -
Dispatchers.IO
indică faptul că această corutină trebuie executată pe o linie rezervată pentru operațiile de I/O.
Funcția login
se execută după cum urmează:
- Aplicația apelează funcția
login
din stratulView
pe firul principal. -
launch
creează o nouă corutină, iar cererea de rețea se faceindependent pe un fir rezervat pentru operațiuni de I/O. - În timp ce corutina se execută, funcția
login
își continuă execuțiași se întoarce, eventual înainte ca cererea de rețea să se termine. Rețineți căpentru simplitate, răspunsul rețelei este ignorat deocamdată.
Din moment ce această cortină este inițiată cu viewModelScope
, ea este executată în domeniul de aplicare al ViewModel
. Dacă ViewModel
este distrus pentru că utilizatorul navighează departe de ecran, viewModelScope
este automat anulat, iar toate corutinele care rulează sunt de asemenea anulate.
O problemă cu exemplul anterior este că orice lucru care apeleazămakeLoginRequest
trebuie să-și amintească să mute în mod explicit execuția în afara firului principal. Să vedem cum putem modifica Repository
pentru a rezolva această problemă pentru noi.
Utilizați corutine pentru siguranța principală
Considerăm că o funcție este sigură din punct de vedere principal atunci când nu blochează actualizările UI pe firul principal. Funcția makeLoginRequest
nu este main-safe, deoarece apelareamakeLoginRequest
de pe firul principal blochează UI. Folosiți funcțiawithContext()
din biblioteca de corutine pentru a muta execuția unei corutine pe un fir de execuție diferit:
class LoginRepository(...) { ... suspend fun makeLoginRequest( jsonBody: String ): Result<LoginResponse> { // Move the execution of the coroutine to the I/O dispatcher return withContext(Dispatchers.IO) { // Blocking network request code } }}
withContext(Dispatchers.IO)
mută execuția corutinei pe un fir de execuțieI/O, ceea ce face ca funcția noastră de apelare să fie sigură din punct de vedere principal și permite actualizarea interfeței utilizator după cum este necesar.
makeLoginRequest
este, de asemenea, marcată cu cuvântul cheie suspend
. Acest cuvânt cheieeste modalitatea lui Kotlin de a impune ca o funcție să fie apelată din interiorul unei corutine.
În exemplul următor, corutina este creată în LoginViewModel
.Deoarece makeLoginRequest
mută execuția în afara firului principal, corutina din funcția login
poate fi acum executată în firul principal:
class LoginViewModel( private val loginRepository: LoginRepository): ViewModel() { fun login(username: String, token: String) { // Create a new coroutine on the UI thread viewModelScope.launch { val jsonBody = "{ username: \"$username\", token: \"$token\"}" // Make the network call and suspend execution until it finishes val result = loginRepository.makeLoginRequest(jsonBody) // Display result of the network request to the user when (result) { is Result.Success<LoginResponse> -> // Happy path else -> // Show error in UI } } }}
Rețineți că corutina este încă necesară aici, deoarece makeLoginRequest
este o funcție suspend
, iar toate funcțiile suspend
trebuie executate într-o corutină.
Acest cod diferă de exemplul anterior login
în câteva moduri:
-
launch
launch
nu acceptă un parametruDispatchers.IO
. Atunci când nu treceți unDispatcher
lalaunch
, orice corutine lansate dinviewModelScope
se execută în firul principal. - Rezultatul solicitării de rețea este acum gestionat pentru a afișa UI de eșec succesor.
Funcția de conectare se execută acum după cum urmează:
- Aplicația apelează funcția
login()
din stratulView
pe firul principal. -
launch
creează o nouă cortină pentru a face cererea de rețea pe firul principal, iar cortina începe execuția. - În cadrul corutinei, apelul la
loginRepository.makeLoginRequest()
now suspendă continuarea execuției corutinei până când bloculwithContext
dinmakeLoginRequest()
se termină. - După ce blocul
withContext
se termină, corutina dinlogin()
își reiaexecutarea pe firul principal cu rezultatul cererii de rețea.
Manipularea excepțiilor
Pentru a manipula excepțiile pe care stratul Repository
le poate arunca, folosiți suportul încorporat de Kotlin pentru excepții.În exemplul următor, folosim un bloc try-catch
:
class LoginViewModel( private val loginRepository: LoginRepository): ViewModel() { fun makeLoginRequest(username: String, token: String) { viewModelScope.launch { val jsonBody = "{ username: \"$username\", token: \"$token\"}" val result = try { loginRepository.makeLoginRequest(jsonBody) } catch(e: Exception) { Result.Error(Exception("Network request failed")) } when (result) { is Result.Success<LoginResponse> -> // Happy path else -> // Show error in UI } } }}
În acest exemplu, orice excepție neașteptată aruncată de apelul makeLoginRequest()
este tratată ca o eroare în UI.
.
Leave a Reply