Kotlin 协程总结,android商城app项目实战

  • 3.协程具体怎么用

    • a.添加依赖
  • b.开始使用

  • c.suspend

  • 4.小结

  • 二、挂起是什么

      • 1.「挂起」的本质
    • 线程:
  • 协程:

  • 2.怎么就「挂起」了?

  • 3.suspend 的意义?

  • 4.怎么自定义 suspend 函数?

    • a.什么时候需要自定义 suspend 函数
  • a.具体该怎么写

  • 5.小结

  • 三、挂起的非阻塞式是怎么回事

      • 1.什么是「非阻塞式挂起」
  • 2.为什么要讲非阻塞式挂起

  • 3.协程与线程

  • 4.小结

  • 四、总结

一、协程是什么

==================================================================

1.简介

协程并不是 Kotlin 提出来的新概念,其他的一些编程语言,例如:Go、Python 等都可以在语言层面上实现协程,甚至是 Java,也可以通过使用扩展库来间接地支持协程。

「协程 Coroutines」源自 Simula 和 Modula-2 语言,这个术语早在 1958 年就被 Melvin Edward Conway 发明并用于构建汇编程序,说明协程是一种编程思想,并不局限于特定的语言。

当我们在了解协程的时候,不可避免的会跟线程进程做比较做分析,下面来贴个图便于理解

在这里插入图片描述

Android 开发者的角度去理解它们的关系:

  • 我们所有的代码都是跑在线程中的,而线程是跑在进程中的。

  • 协程没有直接和操作系统关联,但它不是空中楼阁,它也是跑在线程中的,可以是单线程,也可以是多线程。

  • 单线程中的协程总的执行时间并不会比不用协程少。

  • Android 系统上,如果在主线程进行网络请求,会抛出NetworkOnMainThreadException,对于在主线程上的协程也不例外,这种场景使用协程还是要切线程的。

协程设计的初衷是为了解决并发问题,让 「协作式多任务」 实现起来更加方便。

协程就是 Kotlin 提供的一套线程封装的 API,但并不是说协程就是为线程而生的。

不过,我们学习 Kotlin 中的协程,一开始确实可以从线程控制的角度来切入。因为在 Kotlin 中,协程的一个典型的使用场景就是线程控制。就像 Java 中的Executor 和 Android 中的AsyncTask,Kotlin 中的协程也有对 Thread API 的封装,让我们可以在写代码时,不用关注多线程就能够很方便地写出并发操作。

下面的例子是使用协程进行网络请求获取用户信息并显示到 UI 控件上:

launch({

val user = api.getUser() // 👈 网络请求(IO 线程)

nameTv.text = user.name // 👈 更新 UI(主线程)

})

这里只是展示了一个代码片段,launch并不是一个顶层函数,它必须在一个对象中使用,launch 函数加上实现在 {} 中具体的逻辑,就构成了一个协程。

通常我们做网络请求,要不就传一个 callback,要不就是在 IO 线程里进行阻塞式的同步调用,而在这段代码中,上下两个语句分别工作在两个线程里,但写法上看起来和普通的单线程代码一样。

这里的api.getUser是一个挂起函数,所以能够保证nameTv.text的正确赋值,这就涉及到了协程中最著名的「非阻塞式挂起」。这个名词看起来不是那么容易理解,后面会讲。现在先把这个概念放下,只需要记住协程就是这样写的就行了。

这种「用同步的方式写异步的代码」看起来很方便吧,那么我们来看看协程具体好在哪。

2.协程好在哪

前面提到,launch 函数不是顶层函数,是不能直接用的,可以使用下面三种方法来创建协程:

// 方法一,使用 runBlocking 顶层函数

runBlocking {

getImage(imageId)

}

// 方法二,使用 GlobalScope 单例对象

// 👇 可以直接调用 launch 开启协程

GlobalScope.launch {

getImage(imageId)

}

// 方法三,自行通过 CoroutineContext 创建一个 CoroutineScope 对象

// 👇 需要一个类型为 CoroutineContext 的参数

val coroutineScope = CoroutineScope(context)

coroutineScope.launch {

getImage(imageId)

}

  • 方法一通常适用于单元测试的场景,而业务开发中不会用到这种方法,因为它是线程阻塞的。

  • 方法二和使用 runBlocking 的区别在于不会阻塞线程。但在 Android 开发中同样不推荐这种用法,因为它的生命周期会和 app 一致,且不能取消(什么是协程的取消后面会讲)。

  • 方法三是比较推荐的使用方法,我们可以通过 context 参数去管理和控制协程的生命周期(这里的 context 和 Android 里的不是一个东西,是一个更通用的概念,会有一个 Android 平台的封装来配合使用)。

协程最常用的功能是并发,而并发的典型场景就是多线程。可以使用 Dispatchers.IO参数把任务切到 IO 线程执行:

CoroutineScope(Dispatchers.IO).launch {

}

也可以使用 Dispatchers.Main 参数切换到主线程:

CoroutineScope(Dispatchers.Main).launch{

}

所以在「协程是什么」一节中讲到的异步请求的例子完整写出来是这样的:

CoroutineScope(Dispatchers.Main).launch{ // 在主线程开启协程

val user = api.getUser() // IO 线程执行网络请求

nameTv.text = user.name // 主线程更新 UI

}

而通过 Java 实现以上逻辑,我们通常需要这样写:

api.getUser(new Callback() {

@Override

public void success(User user) {

runOnUiThread(new Runnable() {

@Override

public void run() {

nameTv.setText(user.name);

}

})

}

@Override

public void failure(Exception e) {

}

});

这种回调式的写法,打破了代码的顺序结构和完整性,读起来相当难受。

3.协程具体怎么用

a.添加依赖

//依赖协程核心库

implementation ‘org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.5’

//依赖当前平台所对应的平台库

implementation ‘org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.5’

Kotlin 协程是以官方扩展库的形式进行支持的。我们所使用的「核心库」和 「平台库」的版本应该保持一致。

  • 核心库中包含的代码主要是协程的公共 API 部分。有了这一层公共代码,才使得协程在各个平台上的接口得到统一。

  • 平台库中包含的代码主要是协程框架在具体平台的具体实现方式。因为多线程在各个平台的实现方式是有所差异- 的。

b.开始使用

协程最简单的使用方法,其实在前面章节就已经看到了。我们可以通过一个 launch函数实现线程切换的功能:

CoroutineScope(Dispatchers.IO).launch {

}

这个 launch 函数,它具体的含义是:我要创建一个新的协程,并在指定的线程上运行它。这个被创建、被运行的所谓「协程」是谁?就是你传给 launch 的那些代码,这一段连续代码叫做一个「协程」。

所以,什么时候用协程?当你需要切线程或者指定线程的时候。你要在后台执行任务?切!

launch(Dispatchers.IO) {

val image = getImage(imageId)

}

然后需要在前台更新界面?再切!

CoroutineScope(Dispatchers.IO).launch{

val image = getImage(imageId)

launch(Dispatch.Main) {

avatarIv.setImageBitmap(image)

}

}

好像有点不对劲?这不还是有嵌套嘛。

如果只是使用 launch 函数,协程并不能比线程做更多的事。不过协程中却有一个很实用的函数:withContext 。这个函数可以切换到指定的线程,并在闭包内的逻辑执行结束之后,自动把线程切回去继续执行。那么可以将上面的代码写成这样:

CoroutineScope(Dispatchers.Main).launch { // 👈 在 UI 线程开始

val image = withContext(Dispatchers.IO) { // 👈 切换到 IO 线程,并在执行完成后切回 UI 线程

getImage(imageId) // 👈 将会运行在 IO 线程

}

avatarIv.setImageBitmap(image) // 👈 回到 UI 线程更新 UI

}

这种写法看上去好像和刚才那种区别不大,但如果你需要频繁地进行线程切换,这种写法的优势就会体现出来。可以参考下面的对比:

// 第一种写法

CoroutineScope(Dispatchers.IO).launch {

launch(Dispachers.Main){

launch(Dispachers.IO) {

launch(Dispacher.Main) {

}

}

}

}

// 通过第二种写法来实现相同的逻辑

CoroutineScope(Dispatchers.Main).launch{

withContext(Dispachers.IO) {

}

withContext(Dispachers.IO) {

}

}

由于可以"自动切回来",消除了并发代码在协作时的嵌套。由于消除了嵌套关系,我们甚至可以把 withContext 放进一个单独的函数里面:

launch(Dispachers.Main) { // 👈 在 UI 线程开始

val image = getImage(imageId)

avatarIv.setImageBitmap(image) // 👈 执行结束后,自动切换回 UI 线程

}

// 👇

fun getImage(imageId: Int) = withContext(Dispatchers.IO) {

}

这就是之前说的「用同步的方式写异步的代码」了。

不过如果只是这样写,编译器是会报错的:

fun getImage(imageId: Int) = withContext(Dispatchers.IO) {

// IDE 报错 Suspend function’withContext’ should be called only from a coroutine or another suspend funcion

}

意思是说,withContext 是一个 suspend函数,它需要在协程或者是另一个 suspend函数中调用。

c.suspend

suspend是 Kotlin 协程最核心的关键字,几乎所有介绍 Kotlin 协程的文章和演讲都会提到它。它的中文意思是「暂停」或者「可挂起」。如果你去看一些技术博客或官方文档的时候,大概可以了解到:「代码执行到 suspend 函数的时候会『挂起』,并且这个『挂起』是非阻塞式的,它不会阻塞你当前的线程。」

上面报错的代码,其实只需要在前面加一个suspend 就能够编译通过:

suspend fun getImage(imageId: Int) = withContext(Dispatchers.IO) {

}

suspend具体是什么,下面介绍。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

尾声

如果你想成为一个优秀的 Android 开发人员,请集中精力,对基础和重要的事情做深度研究。

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。 整理的这些架构技术希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

这里,笔者分享一份从架构哲学的层面来剖析的视频及资料给大家梳理了多年的架构经验,筹备近6个月最新录制的,相信这份视频能给你带来不一样的启发、收获。

Android进阶学习资料库

一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!

大厂面试真题

PS:之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

《2019-2021字节跳动Android面试历年真题解析》

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img
369139)]

Android进阶学习资料库

一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!

[外链图片转存中…(img-2TF0Yy32-1712652369140)]

大厂面试真题

PS:之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

[外链图片转存中…(img-2vvCbcxY-1712652369140)]

《2019-2021字节跳动Android面试历年真题解析》

[外链图片转存中…(img-rT6goE9X-1712652369140)]

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-KmK6hMBg-1712652369140)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值