12.1 Android中协程的基本使用

文章目录

前言

在使用协程的时候一直没有一个具体的概念,只知道协程能够使得异步操作等同于同步操作,且不会造成线程阻塞,且第一次使用协程是根据前辈的代码修改而学会的,没有一个规范性的思维,通过查看官方资料将协程的使用重新梳理一遍,一个简单,不麻烦的做法。

1、导入依赖

选择依赖的方式主要分为两种,查看创建项目的时候中Build configuration language的选项选择的某一项,根据项目来选择

Groovy:

kotlin 复制代码
dependencies {
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
}

kotlin:

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

2、使用协程获取服务器中的数据

目的: 防止直接在UI线程中进行网络请求出现线程阻塞,导致应用程序无响应(ANR)

Dispatchers.IO

用于执行I/O操作,如读写文件、访问数据库等。

这个调度器会尝试在后台线程池中执行I/O密集型任务,从而避免阻塞主线程

Dispatchers.Main:

用于在Android的主线程(UI线程)上执行协程。

这个调度器确保与UI相关的操作(如更新UI元素)在主线程上执行。

在非Android平台上(如桌面应用或纯Kotlin项目),Dispatchers.Main 可能不可用或行为不同。

Dispatchers.Default:

用于执行计算密集型任务。可用于执行网络请求,获取服务器数据

这个调度器使用共享的后台线程池来执行协程,从而优化CPU资源的利用。

它是默认的调度器,如果你没有为协程指定调度器,那么它将使用 Dispatchers.Default。

Dispatchers.Unconfined:
不绑定到任何特定的线程

协程在哪个线程上启动,它就会在那个线程上继续执行,直到遇到挂起点(如 suspend 函数调用)。

一旦协程挂起并恢复,它可能会在任何线程上继续执行,这取决于挂起函数的实现。

通常不建议在生产代码中使用 Dispatchers.Unconfined,因为它可能导致难以追踪的线程安全问题。
用法:

kotlin 复制代码
// 创建一个协程作用域(通常是在ViewModel、Activity或Fragment中)
val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
 
// 在协程作用域中启动一个协程
coroutineScope.launch(Dispatchers.IO) {
    // 执行I/O操作
}

2.1 定义请求回调结果的数据类

sealed 关键字

sealed 关键字用于定义一个密封类(sealed class),用于控制子类的个数,只接受子类在密封类的同一个文件中声明,或者作为密封类的嵌套类。

有助于改进when表达式,当使用密封类的时候,编译器会确保when表达式覆盖所有可能得子类,如果没有覆盖的话,则会出现报错的提醒,这有助于提高代码的健壮性和可维护性。

kotlin 复制代码
sealed class Result<out R> {
    data class Success<out T>(val data: T) : Result<T>()
    data class Error(val exception: Exception) : Result<Nothing>()
}

2.2 网络请求

为了防止主线程调用 makeLoginRequest 后出现阻塞界面情况。可以使用协程库中的 withContext() 函数将协程的执行操作移至其他线程,在其中执行耗时的网络请求操作

kotlin 复制代码
class LoginRepository(private val responseParser: LoginResponseParser) {
    private const val loginUrl = "https://example.com/login"

   
 suspend fun makeLoginRequest(
        jsonBody: String
    ): Result<LoginResponse> {
    return withContext(Dispatchers.IO) {
            // Blocking network request code
               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"))
        }
     
    }
}

3、网络回调结构

假设请求回调的结构为:

kotlin 复制代码
{
            "success": true,
            "user": {
                "id": 123,
                "username": "exampleUser",
                "email": "example@example.com"
            },
            "token": "some_jwt_token"
 }

为了适配并处理网络请求回调的数据,需要创建数据类,方便适配回调数据。

kotlin 复制代码
data class User(
    val id: Int,
    val username: String,
    val email: String
)
 
data class LoginResponse(
    val success: Boolean,
    val user: User?,
    val token: String?
)

将网络请求返回的InoutStream通过LoginResponseParser 处理,读取返回的数据并进行处理,转为LoginResponse的数据。

kotlin 复制代码
import com.google.gson.Gson
import com.google.gson.JsonSyntaxException
import java.io.BufferedReader
import java.io.InputStream
import java.io.InputStreamReader

object LoginResponseParser {

    private val gson = Gson()

    @Throws(JsonSyntaxException::class)
    fun parse(inputStream: InputStream): LoginResponse {
        return try {
        // 使用try-with-resources语句自动关闭BufferedReader
        gson.fromJson(
        BufferedReader(InputStreamReader(inputStream)).use {it.readText()}
        , 
        LoginResponse::class.java)
    } catch (e: IOException) {
        // 处理IOException,例如记录日志或抛出运行时异常
        throw RuntimeException("Failed to read input stream", e)
    } catch (e: JsonSyntaxException) {
        // 处理JsonSyntaxException,例如记录日志或抛出运行时异常
        throw RuntimeException("Failed to parse JSON", e)
    }
    }
}

4、通过ViewModel处理网络请求数据

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"))
            }
            when (result) {
                is Result.Success<LoginResponse> -> // Happy path
                else -> Result.Error(Exception("reponse is error"))// Show error in UI
            }
        }
    }
}

想要了解更多协程相关的知识点,可以查看官网

相关推荐
openinstall全渠道统计36 分钟前
免填邀请码工具:赋能六大核心场景,重构App增长新模型
android·ios·harmonyos
双鱼大猫1 小时前
一句话说透Android里面的ServiceManager的注册服务
android
双鱼大猫1 小时前
一句话说透Android里面的SystemServer进程的作用
android
双鱼大猫1 小时前
一句话说透Android里面的View的绘制流程和实现原理
android
双鱼大猫2 小时前
一句话说透Android里面的Window的内部机制
android
双鱼大猫2 小时前
一句话说透Android里面的为什么要设计Window?
android
双鱼大猫2 小时前
一句话说透Android里面的主线程创建时机,frameworks层面分析
android
苏金标3 小时前
android 快速定位当前页面
android
雾里看山6 小时前
【MySQL】内置函数
android·数据库·mysql
风浅月明6 小时前
[Android]页面间传递model列表
android