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 ViewModel
Architektura 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 vrstvyView
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á parametrDispatchers.IO
. Pokud nepředáte parametrDispatcher
dolaunch
, všechny koroutiny spuštěné zviewModelScope
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 vrstvyView
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 blokuwithContext
vmakeLoginRequest()
. - Jakmile blok
withContext
skončí, koroutin vlogin()
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 Repository
vyhodit, 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