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

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

相关推荐
千里马学框架6 小时前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台6 小时前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone7 小时前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc7 小时前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo9 小时前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077009 小时前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼10 小时前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone11 小时前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen11 小时前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone11 小时前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui