Kotlinské korotiny na Androidu

Kotlin je návrhový vzor souběhu, který můžete na Androidu použít ke zjednodušení kódu, který se vykonává asynchronně.Korotiny byly do Kotlinu přidány ve verzi 1.3 a jsou založeny na zavedených konceptech z jiných jazyků.

Na Androidu pomáhají korotiny spravovat dlouhotrvající úlohy, které by jinak mohly zablokovat hlavní vlákno a způsobit, že vaše aplikace nebude reagovat.Více než 50 % profesionálních vývojářů, kteří používají koroutiny, hlásí zvýšení produktivity.Toto téma popisuje, jak můžete pomocí koroutin v Kotlinu tytoproblémy řešit, což vám umožní psát čistší a stručnější kód aplikace.

Vlastnosti

Koroutiny jsou naším doporučeným řešením pro asynchronní programování naAndroidu. Mezi pozoruhodné vlastnosti patří následující:

  • Lehké: Můžete spustit mnoho koroutin v jednom vlákně díky podpoře pozastavení, které neblokuje vlákno, ve kterém koroutina běží. Pozastavení šetří paměť oproti blokování a zároveň podporuje mnoho souběžných operací.
  • Méně úniků paměti:
  • Vestavěná podpora zrušení: Zrušení se automaticky šíří hierarchií běžících korutin.
  • Integrace Jetpack: Mnoho knihoven Jetpack obsahujerozšíření, která poskytují plnou podporu koroutin. Některéknihovny také poskytují svůj vlastníobor korutin, který můžetevyužít pro strukturovanou souběžnost.

Přehled příkladů

Na základě Průvodce architekturou aplikací příkladyv tomto tématu provedou síťový požadavek a vrátí výsledek do hlavníhovlákna, kde pak aplikace může výsledek zobrazit uživateli.

Konkrétně komponenta ViewModelArchitektura volá vrstvu úložiště v hlavním vlákně, aby vyvolala síťový požadavek. Tento průvodce iteruje různá řešení, která pomocí koroutinů udržují hlavní vlákno odblokované.

ViewModel obsahuje sadu rozšíření KTX, která pracují přímo s koroutiny. Tato rozšíření jsoulifecycle-viewmodel-ktx knihovnou a jsou použitav této příručce.

Informace o závislostech

Chcete-li ve svém projektu pro Android používat coroutines, přidejte do souboru své aplikace build.gradle následující závislost:

dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'}

Provádění ve vlákně na pozadí

Provedení síťového požadavku v hlavním vlákně způsobí, že bude čekat neboli blokovat, dokud nedostane odpověď. Protože je vlákno zablokováno, operační systém nemá volat onDraw(), což způsobí zamrznutí aplikace a potenciálněvede k dialogu Application Not Responding (ANR). Pro lepší uživatelskou zkušenost spusťme tuto operaci na vlákně na pozadí.

Nejprve se podívejme na naši třídu Repository a zjistěme, jak provádí síťový požadavek:

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 je synchronní a blokuje volající vlákno. Pro modelováníodpovědi na síťový požadavek máme vlastní třídu Result.

Třída ViewModel spustí síťový požadavek, když uživatel klikne například na tlačítko:

class LoginViewModel( private val loginRepository: LoginRepository): ViewModel() { fun login(username: String, token: String) { val jsonBody = "{ username: \"$username\", token: \"$token\"}" loginRepository.makeLoginRequest(jsonBody) }}

Podle předchozího kódu LoginViewModel blokuje vlákno uživatelského rozhraní při provádění síťového požadavku. Nejjednodušším řešením, jak přesunout provádění mimo hlavní vlákno, je vytvořit novou koroutinu a provést síťovýpožadavek ve vlákně I/O:

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) } }}

Prozkoumejme kód koroutiny ve funkci login:

  • viewModelScope je předdefinovaná CoroutineScope, která je součástí rozšíření ViewModel KTX. Všimněte si, že všechny coroutines musí běžet v ascope. CoroutineScope spravuje jednu nebo více souvisejících koroutin.
  • launch je funkce, která vytváří koroutinu a odesílávykonání jejího těla funkce příslušnému dispečerovi.
  • Dispatchers.IO označuje, že tato koroutina by měla být vykonávána ve vlákně vyhrazeném pro I/O operace.

Funkce login se provede takto:

  • Aplikace zavolá funkci login z vrstvy View na hlavním vlákně.
  • launch Vytvoří novou koroutinu a síťový požadavek se provede nezávisle na vlákně vyhrazeném pro I/O operace.
  • Při běhu koroutiny funkce login pokračuje ve vykonávání a vrátí se, případně ještě před dokončením síťového požadavku. Všimněte si, že pro zjednodušení je síťová odpověď prozatím ignorována.

Protože je tato koroutina spuštěna pomocí viewModelScope, provádí se voblasti působnosti ViewModel. Pokud je ViewModel zničena, protože uživatel odchází z obrazovky, viewModelScope je automatickyzrušena a všechny běžící koroutiny jsou zrušeny také.

Jedním problémem předchozího příkladu je, že cokoli, co volámakeLoginRequest, musí pamatovat na explicitní přesunutí provádění mimo hlavní vlákno. Podívejme se, jak můžeme upravit Repository, aby nám tento problém vyřešil.

Použití coroutines pro main-safety

Funkci považujeme za main-safe, pokud neblokuje aktualizace uživatelského rozhraní v hlavním vlákně. Funkce makeLoginRequest není main-safe, protože volánímakeLoginRequest z hlavního vlákna blokuje uživatelské rozhraní. K přesunutí provádění koroutiny do jiného vlákna použijte funkciwithContext() z knihovny coroutines:

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) přesune provádění koroutiny do vláknaI/O, čímž se naše volání funkce stane main-safe a umožní toupdate uživatelského rozhraní podle potřeby.

makeLoginRequest je také označena klíčovým slovem suspend. Toto klíčové slovoje způsob, jak v jazyce Kotlin vynutit volání funkce zevnitř koroutiny.

V následujícím příkladu je koroutina vytvořena v LoginViewModel.Protože makeLoginRequest přesouvá provádění mimo hlavní vlákno, může být nyní coroutineve funkci login prováděn v hlavním vlákně:

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 } } }}

Všimněte si, že coroutine je zde stále potřeba, protože makeLoginRequest je funkce suspend a všechny funkce suspend musí být prováděny v coroutine.

Tento kód se od předchozího příkladu login liší několika způsoby:

  • launch nepřijímá parametr Dispatchers.IO. Pokud nepředáte parametr Dispatcher do launch, všechny koroutiny spuštěné z viewModelScope poběží v hlavním vlákně.
  • Výsledek síťového požadavku je nyní zpracován tak, aby se zobrazilo uživatelské rozhraní následníka selhání.

Přihlašovací funkce se nyní provádí takto:

  • Aplikace volá funkci login() z vrstvy View v hlavním vlákně.
  • launch Vytvoří novou koroutinu pro provedení síťového požadavku v hlavním vlákně a koroutina se začne provádět.
  • V rámci koroutinu volání loginRepository.makeLoginRequest()nově pozastaví další provádění koroutinu, dokud neskončí běh bloku withContext v makeLoginRequest().
  • Jakmile blok withContext skončí, koroutin v login() obnoví provádění na hlavním vlákně s výsledkem síťového požadavku.

Obsluha výjimek

Pro obsluhu výjimek, které může vrstva Repositoryvyhodit, použijte vestavěnou podporu výjimek v jazyce Kotlin. v následujícím příkladu použijeme blok 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 } } }}

V tomto příkladu je každá neočekávaná výjimka vyhozená voláním makeLoginRequest() zpracována jako chyba v uživatelském rozhraní.

.

Leave a Reply