Kotlin编程第一课--(协程篇)23 异常:try-catch 坑!
这节课我们来学习 Kotlin 协程的异常处理。
其实到这里,我们就已经学完所有 Kotlin 协程的语法知识了。但在真正把 Kotlin 协程应用到生产环境之前,我们还需要掌握一个重要知识点,那就是异常处理。
比起 Kotlin 协程的语法知识点,协程的异常处理,其实更难掌握。在前面的课程中,我们已经了解到:协程就是互相协作的程序,协程是结构化的。正因为 Kotlin 协程有这两个特点,这就导致它的异常处理机制与我们普通的程序完全不一样。
换句话说:如果把 Java 里的那一套异常处理机制,照搬到 Kotlin 协程里来,你一定会四处碰壁。因为在普通的程序当中,你使用 try-catch 就能解决大部分的异常处理问题,但是在协程当中,根据不同的协程特性,它的异常处理策略是随之变化的。
我自己在工作中就踩过很多这方面的坑,遇到过各种匪夷所思的问题:协程无法取消、try-catch 不起作用导致线上崩溃率突然大增、软件功能错乱却追踪不到任何异常信息,等等。说实话,Kotlin 协程的普及率之所以不高,很大一部分原因也是因为它的异常处理机制太复杂了,稍有不慎就可能会掉坑里去。
那么今天这节 ...
Kotlin编程第一课--(协程篇)22 并发:协程不需要处理同步吗?
今天我们来讲讲协程的并发。
在大型软件的架构当中,并发也是一个不可避免的问题。然而,在传统的 Java 编程当中,并发却是个令人生畏的话题。因为 Java 的线程模型、内存模型、同步机制太复杂了,而当复杂的业务逻辑与复杂的并发模型混合在一起的时候,情况就更糟糕了!如果你用 Java 做过中大型软件,对此一定会深有体会。
我们都知道,Kotlin 的协程仍然是基于线程运行的。但是,经过一层封装以后,Kotlin 协程面对并发问题的时候,它的处理手段其实跟 Java 就大不一样。所以这节课,我们就来看看协程在并发问题上的处理,一起来探究下 Kotlin 协程的并发思路,从而真正解决并发的难题。
协程与并发在 Java 世界里,并发往往需要多个线程一起工作,而多线程往往就会有共享的状态,这时候程序就要处理同步问题了。很多初学者在这一步,都会把协程与线程的概念混淆在一起。比如你可以来看看下面这段代码,你觉得有多线程同步的问题吗?
// 代码段1fun main() = runBlocking { var i = 0 // Default 线程池 launch(Di ...
Kotlin编程第一课--(协程篇)21 select:到底是在选择什么?
今天我们来学习 Kotlin 协程的 select。
select,在目前的 Kotlin 1.6 当中,仍然是一个实验性的特性(Experimental)。但是,考虑到 select 具有较强的实用性,我决定还是来给你介绍一下它。
select 可以说是软件架构当中非常重要的一个组件,在很多业务场景下,select 与 Deferred、Channel 结合以后,在大大提升程序的响应速度的同时,还可以提高程序的灵活性、扩展性。
今天这节课,我会从 select 的使用角度着手,带你理解 select 的核心使用场景,之后也会通过源码帮你进一步分析 select API 的底层规律。学完这节课以后,你完全可以将 select 应用到自己的工作当中去。
好,接下来,我们就一起来学习 select 吧!
select 就是选择“更快的结果”
由于 select 的工作机制比较抽象,我们先来假设一个场景,看看 select 适用于什么样的场景。
客户端,想要查询一个商品的详情。目前有两个服务:缓存服务,速度快但信息可能是旧的;网络服务,速度慢但信息一定是最新的。
对于这个场景,如果让我 ...
Kotlin编程第一课--(协程篇)20 Flow:为什么说Flow是“冷”的?
今天我们来学习 Kotlin 协程 Flow 的基础知识。
Flow,可以说是在 Kotlin 协程当中自成体系的知识点。Flow 极其强大、极其灵活,在它出现之前,业界还有很多质疑 Kotlin 协程的声音,认为 Kotlin 的挂起函数、结构化并发,并不足以形成核心竞争力,在异步、并发任务的领域,RxJava 可以做得更好。
但是,随着 2019 年 Kotlin 推出 Flow 以后,这样的质疑声就渐渐没有了。有了 Flow 以后,Kotlin 的协程已经没有明显的短板了。简单的异步场景,我们可以直接使用挂起函数、launch、async;至于复杂的异步场景,我们就可以使用 Flow。
实际上,在很多技术领域,Flow 已经开始占领 RxJava 原本的领地,在 Android 领域,Flow 甚至还要取代原本 LiveData 的地位。因为,Flow 是真的香啊!
接下来,我们就一起来学习 Flow。
Flow 就是“数据流”Flow 这个单词有“流”的意思,比如 Cash Flow 代表了“现金流”;Traffic Flow 代表了“车流”;Flow 在 Kotlin 协程当 ...
kotlin编程第一课--(协程篇)19 channel:为什么说channel是热的?
前面我们学习的挂起函数、async,它们一次都只能返回一个结果。但在某些业务场景下,我们往往需要协程返回多个结果,比如微信等软件的 IM 通道接收的消息,或者是手机 GPS 定位返回的经纬度坐标需要实时更新。那么,在这些场景下,我们之前学习的协程知识就无法直接解决了。
而今天我要讲解的 Kotlin 协程中的 Channel,就是专门用来做这种事情的。类似的需求,如果我们不使用 Channel 而是用其他的并发手段配合集合来做的话,其实也能实现,但复杂度会大大增加。那么接下来,我们就一起来学习下 Channel。
Channel 就是管道顾名思义,Channel 就是一个管道。我们可以用这个概念,先来建立一个思维模型:
Channel 这个管道的其中一端,是发送方;管道的另一端是接收方。而管道本身,则可以用来传输数据。
所以,我们根据上面的思维模型,很容易就能写出下面的代码。
// 代码段1fun main() = runBlocking { // 1,创建管道 val channel = Channel<Int>() launch { ...
Kotlin编程第一课--(协程篇)题目解答 期中考试版本参考实现
上节课我给你布置了一份考试题,你完成得怎么样了呢?这节课呢,我会来告诉你我是如何用 Kotlin 来做这个图片处理程序的,供你参考。
由于上节课我们已经做好了前期准备,所以这里我们直接写代码就行了。
1.0 版本对于图片反转和裁切的这个问题,如果一开始你就去想象一个大图片,里面有几万个像素点,那你可能会被吓到。但是,如果你将数据规模缩小,再来分析的话,你会发现这个问题其实很简单。
这里,我们就以一张 4X4 像素的照片为例来分析一下。
这其实就相当于一个抽象的模型,如果我们基于这张 4X4 的照片,继续分析翻转和裁切,就会容易很多。我们可以来画一个简单的图形:
上面这张图,从左到右分别是原图、横向翻转、纵向翻转、裁切。其中,翻转看起来是要复杂一些,而裁切是最简单的。
我们先来处理裁切。对于裁切,其实只需要将图片当中某个部分的像素拷贝到内存,然后存储成为一张新图片就行了。
/** * 图片裁切 */fun Image.crop(startY: Int, startX: Int, width: Int, height: Int): Image { val pixels ...
Kotlin编程第一课--(协程篇)期中考试 用Kotlin实现图片处理程序
不知不觉间,咱们的课程就已经进行一半了,我们已经学完很多内容:
基础篇,我们学完了所有 Kotlin 基础语法和重要特性。
加餐篇,我们学习了 Kotlin 编程的 5 大编程思维:函数式思维、表达式思维、不变性思维、空安全思维、协程思维。
协程篇,我们也已经学完了所有基础的协程概念。
所以现在,是时候来一次阶段性的验收了。这次,我们一起来做一个图片处理程序,来考察一下自己对于 Kotlin 编程知识的理解和应用掌握情况。初始化工程的代码在这里GitHub,你可以像往常那样,将其 clone 下来,然后用 IntelliJ 打开即可。
我们仍然会分为两个版本 1.0、2.0,不过,这一次要轮到你亲自动手写代码了!
1.0 版本:处理本地图片当你将初始化工程打开以后,你会发现“src/main/resources/images/”这个目录下有一张图片:android.png,它就是我们要处理的图片对象。
一般来说,我们想要处理图片,会第一时间想到 Photoshop,但其实简单的图片处理任务,我们完全可以通过代码来实现,比如图片横向翻转、图片 ...
Kotlin编程第一课--(协程篇)18 实战:让KtHttp支持挂起函数
今天这节实战课,我们接着前面第 12 讲里实现的网络请求框架,来进一步完善这个 KtHttp,让它支持挂起函数。
在上一次实战课当中,我们已经开发出了两个版本的 KtHttp,1.0 版本的是基于命令式风格的,2.0 版本的是基于函数式风格的。其中 2.0 版本的代码风格,跟我们平时工作写的代码风格很不一样,之前我也说了,这主要是因为业界对 Kotlin 函数式编程接纳度并不高,所以这节课的代码,我们将基于 1.0 版本的代码继续改造。这样,也能让课程的内容更接地气一些,甚至你都可以借鉴今天写代码的思路,复用到实际的 Android 或者后端开发中去。
跟往常一样,这节课的代码还是会分为两个版本:
3.0 版本,在之前 1.0 版本的基础上,扩展出异步请求的能力。
4.0 版本,进一步扩展异步请求的能力,让它支持挂起函数。
好,接下来就正式开始吧!
3.0 版本:支持异步(Call)有了上一次实战课的基础,这节课就会轻松一些了。关于动态代理、注解、反射之类的知识不会牵涉太多,我们今天主要把精力都集中在协程上来。不过,在正式开始写协程代码之前,我们需要先让 KtHttp 支持异步请求 ...
Kotlin编程第一课--(协程篇)17 Context:万物皆为Context?
今天我们来学习 Kotlin 协程的 Context。
协程的 Context,在 Kotlin 当中有一个具体的名字,叫做 CoroutineContext。它是我们理解 Kotlin 协程非常关键的一环。
从概念上讲,CoroutineContext 很容易理解,它只是个上下文而已,实际开发中它最常见的用处就是切换线程池。不过,CoroutineContext 背后的代码设计其实比较复杂,如果不能深入理解它的设计思想,那我们在后面阅读协程源码,并进一步建立复杂并发结构的时候,都将会困难重重。
所以这节课,我将会从应用的角度出发,带你了解 CoroutineContext 的使用场景,并会对照源码带你理解它的设计思路。另外,知识点之间的串联也是很重要的,所以我还会带你分析它跟我们前面学的 Job、Deferred、launch、async 有什么联系,让你能真正理解和掌握协程的上下文,并建立一个基于 CoroutineContext 的协程知识体系。
Context 的应用前面说过,CoroutineContext 就是协程的上下文。你在前面的第 14~16 讲里其实就已经见过它了。 ...
Kotlin编程第一课--(协程篇)16 Job:协程也有生命周期吗?
今天我们来学习 Kotlin 协程的 Job。
Job 其实就是协程的句柄。从某种程度上讲,当我们用 launch 和 async 创建一个协程以后,同时也会创建一个对应的 Job 对象。另外,Job 也是我们理解协程生命周期、结构化并发的关键知识点。通过 Job 暴露的 API,我们还可以让不同的协程之间互相配合,从而实现更加复杂的功能。
虽然前面已经解释过,Job 就是协程的句柄,但你可能还是不清楚它到底是什么,因为句柄本身就是一个比较“虚”的概念。所以在这节课中,我们会从使用的角度入手,来看看 Job 到底能干什么。在充分理解了 Job 的用法以后,我们再来结合它的源代码进一步分析,这样对 Job 也会有一个更加清晰的认知。
Job 生命周期在上节课我们学习 launch、async 的时候,我们知道它们两个返回值类型分别是 Job 和 Deferred。
// 代码段1public interface Deferred<out T> : Job { public suspend fun await(): T}
而如果你去看 Deferre ...