通勤路上学协程

通勤路上学协程

作者简介:Serpit,Android开发工程师,2023年加入37手游技术部,目前负责国内游戏发行 Android SDK 开发。

前言

此前,在落地WebView离线加载的时候,需要做离线资源包的更新管理。此间涉及许多接口请求和文件资源下载的逻辑,会有频繁的线程切换场景。因此,在这样的需求下,尝试使用了「协程」来作为「线程切换工具」。

什么是「协程」

什么是协程?Google了一下,大多数大佬都说是轻量级线程。心想,这么神奇?在JVM体系下竟然还有比线程颗粒度更小的吗?带着这个疑问,又看了一圈,最后在扔物线大佬的文章中有详细的说明了「协程」和「线程」的关系。这里贴一下

当我们讨论协程和线程的关系时,很容易陷入中文的误区,两者都有一个「程」字,就觉得有关系,其实就英文而言,Coroutines 和 Threads 就是两个概念。 从 Android 开发者的角度去理解它们的关系:

  • 我们所有的代码都是跑在线程中的,而线程是跑在进程中的。
  • 协程没有直接和操作系统关联,但它不是空中楼阁,它也是跑在线程中的,可以是单线程,也可以是多线程。
  • 单线程中的协程总的执行时间并不会比不用协程少。
  • Android 系统上,如果在主线程进行网络请求,会抛出 NetworkOnMainThreadException,对于在主线程上的协程也不例外,这种场景使用协程还是要切线程的。
  • 协程设计的初衷是为了解决并发问题,让 「协作式多任务」 实现起来更加方便

这里解释了协程与线程的关系。总而言之,在Android开发中,是一个让开发者以"同步的代码风格来实现异步逻辑"的工具。

如何使用「协程」

小试牛刀-配置

groovy 复制代码
dependencies {
    ...
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines"
    ...
}

小试牛刀-开启协程

开启一个协程,使用scope的launch函数。

kotlin 复制代码
 MainScope().launch {
    doSomethingInMainThread(); //主线程
 }

小试牛刀-切换线程

使用withContext,可以指定线程

kotlin 复制代码
 MainScope().launch {
    doSomethingInMainThread(); //主线程
    withContext(Dispatchers.IO) {
        doSomethingIoThread(); //子线程
    }
 }

这里可以说明,Dispatchers有几个类型提供使用,可以根据业务场景使用不同的Dispatcher

  • Dispatchers.Default,计算密集型子线程
  • Dispatchers.Main,主线程
  • Dispatchers.IO,IO密集型子线程

小试牛刀-suspend

suspend关键字是kotlin提供给协程的关键字,中文意思是「暂停」或者是「可挂起」的意思。但其实,给方法加suspend关键字,是不会给方法施加魔法让其变成「可挂起」方法的。而这个作用其实是传递的作用。怎么理解,个人的理解是类似"抛异常"这种传递的关系。

比如:

kotlin 复制代码
suspend fun methodA() {}

//此处不加suspend会编译不过,提示:
//Suspend function 'methodA' should be called only from a coroutine or another suspend function
suspend fun methodB() {
    methodA(); 
}

从上述提示,就可看出,要么使用suspend关键字传递下去,要么使用开启协程launch调用suspend方法。这跟"异常"处理是不是比较相似,要么try-catch处理了,要么给方法继续抛异常~

上手简单实战!

小试牛刀了三板斧后,也可以开始上手实战了,这里以此前项目中资源管理的例子,给大家展示一波~

先解释一下业务流程:

  1. 下载资源文件
  2. 下载清单文件(包含资源的一些md5和版本信息)
  3. 校验资源文件
  4. 更新资源到本地目录

上代码!(这里篇幅原因,会使用伪代码进行展示,各位大佬毕竟对于下载和各种IO操作都已经溜得飞起了)

kotlin 复制代码
//下载文件
fun downloadFile(String url) {
    val request = Request.Builder().url(url).build()
    val response = okHttpClient.newCall(request).execute() //⚠️这里使用OkHttp的同步请求方式
    val file = parse(response) //解析请求响应
    return file
}



--------------分割线,划重点--------------

//下载清单文件,suspend方法,调度到IO线程中执行下载
suspend fun downloadManifest(String manifestUrl) {
    return withContext(Dispatchers.IO) {
        return downloadFile(manifestUrl)
    }
}

//下载资源文件,suspend方法,调度到IO线程中执行下载
suspend fun downloadRes(String resUrl) {
    return withContext(Dispatchers.IO) {
        return downloadFile(resUrl)
    }
}

//校验资源文件
suspend fun verifyRes(resFile : File, manifestFile: File) : Boolean {
    return withContext(Dispatchers.IO) {
        String manifest = FileUtils.read(manifestFile) //IO读文件
        val resFileList : List<File> = unzip(resFile);
        var flag = true
        resFileList.forEach { it -> 
            if(!verify(it , manifest)) { //校验文件
                flag  = false
            }
        }
        return flag
    }
}

//更新资源到本地目录
suspend fun updateLocalRes(resFile : File) : Boolean {
    return withContext(Dispatchers.IO) {
        deleteLocalPath() // 删除本地资源
        saveLocalPath(resFile) //从下载目录挪到本地资源路径
        deleteCache() // 删除下载目录的文件
    }
}

fun processDownload() {
    MainScope().launch {
        val manifestFile = downloadManifest(manifestUrl)
        val resFile = downloadRes(resUrl)
        if(!verifyRes(resFile, manifestFile)) { //🎉这里等待两者下载完成后才执行校验逻辑
            updateLocalRes()
        }
    }
}

这样,一个下载-校验-更新的流程就清爽的完成了,全程没有回调!看上去十分简洁。换了以前,至少也得需要三四层的回调嵌套,大大提高了代码的可读性~至此,已经算是入门了协程的门了,但是想要用好协程,我们还需要多学习一些api和原理。比如各种scope(lifecycleScope,GlobalScope等),还有async等挂起函数。甚至探究协程是如何实现这种"同步写异的魔法的。后面会出文章跟大家一起学习,敬请期待~

相关推荐
数据猎手小k2 小时前
AndroidLab:一个系统化的Android代理框架,包含操作环境和可复现的基准测试,支持大型语言模型和多模态模型。
android·人工智能·机器学习·语言模型
你的小103 小时前
JavaWeb项目-----博客系统
android
风和先行3 小时前
adb 命令查看设备存储占用情况
android·adb
AaVictory.4 小时前
Android 开发 Java中 list实现 按照时间格式 yyyy-MM-dd HH:mm 顺序
android·java·list
似霰5 小时前
安卓智能指针sp、wp、RefBase浅析
android·c++·binder
大风起兮云飞扬丶5 小时前
Android——网络请求
android
干一行,爱一行5 小时前
android camera data -> surface 显示
android
断墨先生5 小时前
uniapp—android原生插件开发(3Android真机调试)
android·uni-app
无极程序员7 小时前
PHP常量
android·ide·android studio
58沈剑8 小时前
80后聊架构:架构设计中两个重要指标,延时与吞吐量(Latency vs Throughput) | 架构师之路...
架构