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 ViewModelArhitectură 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 o CoroutineScope predefinită care este inclusă cu extensiile ViewModel KTX. Rețineți că toate corutinele trebuie să ruleze în ascope. Un CoroutineScope 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 stratul View 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 parametru Dispatchers.IO. Atunci când nu treceți un Dispatcher la launch, 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 stratul View 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 blocul withContext din makeLoginRequest() se termină.
  • După ce blocul withContext se termină, corutina din login() îș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