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

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

相关推荐
re1ife3 小时前
Android Studio开发知识:从基础到进阶
android·java·开发语言·android studio
吴同学是个程序员3 小时前
【Android】Android Studio 配置国内镜像源
android·ide·gradle·android studio·hosts
jiet_h7 小时前
使用 Ktor 构建现代 Android 应用的后端服务
android
深漂阿碉10 小时前
Android studio2024的第一个安卓项目
android
zilong_zzz10 小时前
文件IO4(提高LCD显示效率/BMP图像原理与应用)
android
_一条咸鱼_11 小时前
大厂Android面试秘籍:Activity 窗口管理模块(四)
android·面试·android jetpack
无极程序员14 小时前
远程主机可能不符合glibc和libstdc++ VS Code服务器的先决条件
android·java·运维·服务器·php
快乐10114 小时前
Mac下FFmpeg编译和集成
android
_一条咸鱼_15 小时前
Android大厂面试秘籍:不同Android系统版本特性分析
android·面试·android jetpack