Android Kotlin知识汇总(四)Kotlin 协程实践

Kotlin的重要优势及特点之------结构化并发

Kotlin 协程是一种并发设计模式,可以在 Android 平台上让异步代码像阻塞代码一样易于使用。协程可大幅简化后台任务管理,例如网络调用、本地数据访问等任务的管理。

简单来说,协程就是一种轻量级的非阻塞的线程工具API,可以用同步的方式写出异步的代码,优雅地切换线程和处理回调地狱。**与线程的关系,**线程在进程中,协程在线程中。

所有源文件都必须编码为 UTF-8。

来源标注:Android 上的 Kotlin 协程 | Android Developers

书接上篇:Android Kotlin知识汇总(三)Kotlin 协程-CSDN博客


示例概览

根据应用架构指南,本主题中的示例会发出网络请求并将结果返回到主线程,然后应用可以在主线程上向用户显示结果。

具体而言,ViewModel 架构组件会在主线程上调用代码库层,以触发网络请求。ViewModel 包含一组可直接与协程配合使用的 KTX 扩展(lifecycle-viewmodel-ktx 库)。

依赖项信息

如需在 Android 项目中使用协程,请将以下依赖项添加到应用的 build.gradle 文件中:

Kotlin 复制代码
dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9")
}

Repository

创建LoginRepository类,其中makeLoginRequest方法是同步的,并且会阻塞发起调用的线程。为了对网络请求的响应建模,我们创建了自己的 Result 类。

Kotlin 复制代码
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"
    fun makeLoginRequest(jsonBody: String): Result<LoginResponse> {
        //IO操作
        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"))
    }
}

ViewModel类

用于在点击登陆(例如,点击按钮)时触发网络请求:

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

使用上述代码,LoginViewModel 会在网络请求发出时阻塞UI线程。如需将执行操作移出主线程,最简单的方法是创建一个新的协程,然后在 I/O 线程上执行网络请求:

Kotlin 复制代码
class LoginViewModel(private val loginRepository: LoginRepository): ViewModel() {
    fun login(username: String, token: String) {
        // 创建并开启一个 coroutine 协程
        viewModelScope.launch(Dispatchers.IO) {
            val jsonBody = "{ username: \"$username\", token: \"$token\"}"
            loginRepository.makeLoginRequest(jsonBody)
        }
    }
}

由于此协程通过 viewModelScope 启动,因此在 ViewModel 的作用域内执行。如果 ViewModel 因用户离开屏幕而被销毁,则 viewModelScope 会自动取消,且所有运行的协程也会被取消。

使用协程确保主线程安全

makeLoginRequest 函数不是主线程安全的,因为从主线程调用 makeLoginRequest 确实会阻塞界面。可以使用协程库中的 withContext() 函数将协程的执行操作移至其他线程:

Kotlin 复制代码
class LoginRepository(...) {
    ...
    suspend fun makeLoginRequest(jsonBody: String): Result<LoginResponse> {
        return withContext(Dispatchers.IO) {
            //IO操作...
        }
    }
}

makeLoginRequestsuspend 关键字进行标记,强制从协程内调用函数。

Kotlin 复制代码
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\"}"
            val result = loginRepository.makeLoginRequest(jsonBody)
            when (result) {
                is Result.Success<LoginResponse> -> 
                else -> // Show error in UI
            }
        }
    }
}

makeLoginRequest 是一个 suspend 函数,而所有 suspend 函数都必须在协程中执行。launch 不接受 Dispatchers.IO 参数。则从 viewModelScope 启动的所有协程都会在主线程中运行。后续可以处理网络请求的结果,以显示成功或失败界面。

处理异常

为了处理 Repository 层可能抛出的异常,请使用 Kotlin 对异常的内置支持。在以下示例中,我们使用的是 try-catch 块:

Kotlin 复制代码
class LoginViewModel(private val loginRepository: LoginRepository): ViewModel() {
    fun login(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"))
            }
            ...
        }
    }
}

在此示例中,makeLoginRequest() 调用抛出的任何意外异常都会处理为界面错误。

相关推荐
Sinsa_SI2 分钟前
2024年9月中国电子学会青少年软件编程(Python)等级考试试卷(六级)答案 + 解析
开发语言·python·等级考试·电子学会·考级
济南信息学奥赛刘老师2 分钟前
GESP考试大纲
开发语言·c++·算法·青少年编程
老码沉思录5 分钟前
Android开发实战班 -网络编程 - Retrofit 网络请求 + OkHttp 使用详解
android·网络·retrofit
前期后期15 分钟前
Android 工厂设计模式的使用:咖啡机,可以做拿铁,可以做美式等等。
android·java·设计模式
许静知17 分钟前
第十章 JavaScript的应用
开发语言·javascript·ecmascript
froginwe1123 分钟前
SQLite Having 子句
开发语言
好开心3332 分钟前
js高级06-ajax封装和跨域
开发语言·前端·javascript·ajax·okhttp·ecmascript·交互
不惑_38 分钟前
【Python入门第七讲】列表(List)
开发语言·python·list
无空念39 分钟前
C++ STL - vector/list讲解及迭代器失效
开发语言·c++
雪的期许39 分钟前
Python/GoLang/Java 多环境管理工具 pyenv/goenv/jenv
开发语言·python·策略模式