一、协程相关
1-1、协程使用
官网例子(组合挂起函数):https://www.kotlincn.net/docs/reference/coroutines/composing-suspending-functions.html
1、顺序调用:
|
我们使用普通的顺序来进行调用,因为这些代码是运行在协程中的,只要像常规的代码一样 顺序 都是默认的。下面的示例展示了测量执行两个挂起函数所需要的总时间:
|
输出:The answer is 42 Completed in 2017 ms
2、使用Async并发
如果 doSomethingUsefulOne
与 doSomethingUsefulTwo
之间没有依赖,并且我们想更快的得到结果,让它们进行 并发 吗?这就是 async 可以帮助我们的地方。
在概念上,async 就类似于 launch。它启动了一个单独的协程,这是一个轻量级的线程并与其它所有的协程一起并发的工作。不同之处在于 launch
返回一个 Job 并且不附带任何结果值,而 async
返回一个 Deferred —— 一个轻量级的非阻塞 future, 这代表了一个将会在稍后提供结果的 promise。你可以使用 .await()
在一个延期的值上得到它的最终结果, 但是 Deferred
也是一个 Job
,所以如果需要的话,你可以取消它。
|
输出:The answer is 42 Completed in 1017 ms
这里快了两倍,因为两个协程并发执行。
3、协程抛出异常处理
如果在 concurrentSum
函数内部发生了错误,并且它抛出了一个异常, 所有在作用域中启动的协程都会被取消。
|
运行结果:
Second child throws an exception
First child was cancelled
Computation failed with ArithmeticException
1-2、suspend挂起概念
仍物线(朱凯):https://kaixue.io/kotlin-coroutines-2/
suspend是协程的关键字,每一个被suspend修饰的方法都必须在另一个suspend函数或者Coroutine协程程序中进行调用。
1、挂起的本质:
协程中「挂起」的对象到底是什么?挂起线程,还是挂起函数?都不对,我们挂起的对象是协程。
还记得协程是什么吗?启动一个协程可以使用 launch
或者 async
函数,协程其实就是这两个函数中闭包的代码块。
launch
,async
或者其他函数创建的协程,在执行到某一个 suspend
函数的时候,这个协程会被「suspend」,也就是被挂起。
那此时又是从哪里挂起?从当前线程挂起。换句话说,就是这个协程从正在执行它的线程上脱离。
注意,不是这个协程停下来了!是脱离,当前线程不再管这个协程要去做什么了。
suspend 是有暂停的意思,但我们在协程中应该理解为:当线程执行到协程的 suspend 函数的时候,暂时不继续执行协程代码了。
我们先让时间静止,然后兵分两路,分别看看这两个互相脱离的线程和协程接下来将会发生什么事情:
挂起后线程接着干了啥:
线程执行到了 suspend 函数这里的时候,就暂时不再执行剩余的协程代码,执行协程代码块以外的工作。
挂起后协程干了啥:
它从 suspend
函数开始脱离启动它的线程,继续执行在 Dispatchers
协程的代码在到达 suspend
函数的时候被掐断,接下来协程会从这个 suspend
函数开始继续往下执行,不过是在指定的线程。
协程在执行到有 suspend 标记的函数的时候,会被 suspend 也就是被挂起,而所谓的被挂起,就是切个线程;
不过区别在于,挂起函数在执行完成之后,协程会重新切回它原先的线程。
再简单来讲,在 Kotlin 中所谓的挂起,就是一个稍后会被自动切回来的线程调度操作。
2、协程与线程
下面来看下官方的原话:协程是一种并发设计模式,您可以在 Android 平台上使用它来简化异步执行的代码。
只不过呢,协程它不仅提供了方便的 API,在设计思想上是一个基于线程的上层框架,你可以理解为新造了一些概念用来帮助你更好地使用这些 API,仅此而已。类似于 Java 自带的 Executor 系列 API 或者 Android 的 Handler 系列 API。
说到这里,Kotlin 协程的三大疑问:协程是什么、挂起是什么、挂起的非阻塞式是怎么回事,非常简单:
- 协程就是切线程;
- 挂起就是可以自动切回来的切线程;
- 挂起的非阻塞式指的是它能用看起来阻塞的代码写出非阻塞的操作
二、更好调用的函数(默认参数值、@JvmOverloads、显示参数名)
Kotlin的函数更加好调用,主要是表现在两个方面:1,显式的标示参数名,可以方便代码阅读;2,函数可以有默认参数值,可以大大减少Java中的函数重载。
|
在kotlin中调用:
第二个参数没有默认值,则调用时必须有一个参数
|
在java中调用:
在java与kotlin的混合项目中,会发现用kotlin实现的带默认参数的函数,在java中去调用的化就不能利用这个特性了,还是需要给所有参数赋值
这时候可以在kotlin的函数前添加注解@JvmOverloads,添加注解后翻译为class的时候kotlin会帮你去生成多个函数实现函数重载,
添加@JvmOverloads后,反编译后可以看到帮助我们生成多个参数的重载函数,
然后在java中也可以如下调用,
注意:
- 原函数第二个参数没有默认值,因此这里如果只传一个参数则默认是第二个参数:
- java中无法指定参数位置
三、使用data class来快速实现model类
在java中要声明一个model类需要实现很多的代码,首先需要将变量声明为private,然后需要实现get和set方法,还要实现对应的hashcode equals toString方法等
如果用kotlin,只需要一行代码就可以做到。
|
对于Kotlin中的类,会为它的参数自动实现get set方法。而如果加上data关键字,还会自动生成equals hashcode toString。原理其实数据类中的大部分代码都是模版代码,Kotlin聪明的将这个模版代码的实现放在了编译器处理的阶段。
坑点:
data class使用fastJson转换数据时会报如下错误,fastjson反射获取无参构造函数,而data class至少有一个有参构造函数,
|
解决方案:
1、无法使用dataclass,使用普通class即可,手写toString
2、替换为Gson等其他库
3、使用fastJson某一低版本、所有参数添加默认值生成无参构造等方式(试了生成了无参构造,但仍报错)
https://www.dazhuanlan.com/2019/10/16/5da62b6c48adc/
https://www.jianshu.com/p/38219b28efb9
四、扩展函数
4-1、自定义的扩展函数
例一:如String类中,我们想获取最后一个字符,String中没有这样的直接函数,你可以用.后声明这样一个扩展函数:
|
这样定义好lastChar()函数后,之后只需要import进来后,就可以用String类直接调用该函数了,跟调用它自己的方法没有区别。这样可以避免重复代码和一些静态工具类,而且代码更加简洁明了。
例二:协程和视图生命周期相绑定
若协程用于异步加载一张图片,这张图片显示在 ImageView 上。当 ImageView 所在界面已被销毁时,得及时取消协程的加载任务,以释放资源:
|
为Job扩展方式,传入 View 类型的参数,表示该Job和该 View 的生命周期绑定,当 View 生命周期结束时,自动取消协程。
4-2、Kotlin的源码中定义的常用的扩展函数also、apply、let、run
apply函数的作用是:调用某对象的apply函数,在函数范围内,可以任意调用该对象的任意方法,并返回该对象。 从结构上来看apply函数和run函数很像,唯一不同点就是它们各自返回的值不一样,run函数是以闭包形式返回最后一行代码的值,而apply函数的返回的是传入对象的本身。 apply一般用于一个对象实例初始化的时候,需要对对象中的属性进行赋值。或者动态inflate出一个XML的View的时候需要给View绑定数据也会用到,这种情景非常常见。
函数 |
是否是扩展函数 |
函数参数(this、it) |
返回值(调用本身、最后一行) |
---|---|---|---|
with | 不是 | this | 最后一行 |
T.run | 是 | this | 最后一行 |
T.let | 是 | it | 最后一行 |
T.also | 是 | it | 调用本身 |
T.apply | 是 | this | 调用本身 |
五、懒初始化byLazy和延迟初始化lateinit
5-1 懒初始化by lazy
懒初始化是指推迟一个变量的初始化时机,变量在使用的时候才去实例化,这样会更加的高效。因为我们通常会遇到这样的情况,一个变量直到使用时才需要被初始化,或者仅仅是它的初始化依赖于某些无法立即获得的上下文。
5-2 延迟初始化lateinit
另外,对于var的变量,如果类型是非空的,是必须初始化的,不然编译不通过,这时候需要用到lateinit延迟初始化,使用的时候再去实例化。
5-3 by lazy 和 lateinit 的区别
-
by lazy 修饰val的变量
-
lateinit 修饰var的变量,且变量是非空的类型