官网例子(组合挂起函数):https://www.kotlincn.net/docs/reference/coroutines/composing-suspending-functions.html
1、顺序调用:
suspend fun doSomethingUsefulOne(): Int {
delay(1000L)
// 假设我们在这里做了一些有用的事
return
13
}
suspend fun doSomethingUsefulTwo(): Int {
delay(1000L)
// 假设我们在这里也做了一些有用的事
return
29
}
我们使用普通的顺序来进行调用,因为这些代码是运行在协程中的,只要像常规的代码一样 顺序 都是默认的。下面的示例展示了测量执行两个挂起函数所需要的总时间:
val time = measureTimeMillis {
val one = doSomethingUsefulOne()
val two = doSomethingUsefulTwo()
println(
"The answer is ${one + two}"
)
}
println(
"Completed in $time ms"
)
输出:The answer is 42 Completed in 2017 ms
2、使用Async并发
如果 doSomethingUsefulOne
与 doSomethingUsefulTwo
之间没有依赖,并且我们想更快的得到结果,让它们进行 并发 吗?这就是 async 可以帮助我们的地方。
在概念上,async 就类似于 launch。它启动了一个单独的协程,这是一个轻量级的线程并与其它所有的协程一起并发的工作。不同之处在于 launch
返回一个 Job 并且不附带任何结果值,而 async
返回一个 Deferred —— 一个轻量级的非阻塞 future, 这代表了一个将会在稍后提供结果的 promise。你可以使用 .await()
在一个延期的值上得到它的最终结果, 但是 Deferred
也是一个 Job
,所以如果需要的话,你可以取消它。
suspend fun concurrentSum(): Int = coroutineScope {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
one.await() + two.await()
}
输出:The answer is 42 Completed in 1017 ms
这里快了两倍,因为两个协程并发执行。
3、协程抛出异常处理
如果在 concurrentSum
函数内部发生了错误,并且它抛出了一个异常, 所有在作用域中启动的协程都会被取消。
fun main() = runBlocking {
try
{
failedConcurrentSum()
}
catch
(e: ArithmeticException) {
println(
"Computation failed with ArithmeticException"
)
}
}
suspend fun failedConcurrentSum(): Int = coroutineScope {
val one = async {
try
{
delay(Long.MAX_VALUE)
// 模拟一个长时间的运算
42
}
finally
{
println(
"First child was cancelled"
)
}
}
val two = async {
println(
"Second child throws an exception"
)
throw
ArithmeticException()
}
one.await() + two.await()
}
运行结果:
Second child throws an exception
First child was cancelled
Computation failed with ArithmeticException
1-2、suspend挂起概念仍物线(朱凯):Kotlin 协程的挂起好神奇好难懂?今天我把它的皮给扒了
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 协程的三大疑问:协程是什么、挂起是什么、挂起的非阻塞式是怎么回事,非常简单:
- 协程就是切线程;
- 挂起就是可以自动切回来的切线程;
- 挂起的非阻塞式指的是它能用看起来阻塞的代码写出非阻塞的操作
Kotlin的函数更加好调用,主要是表现在两个方面:1,显式的标示参数名,可以方便代码阅读;2,函数可以有默认参数值,可以大大减少Java中的函数重载。
@JvmOverloads
fun joinToString(
param1: String =
""
,
param2: String,
param3: String =
""
,
param4: String =
""
): String {
return
StringBuilder()
.append(param1)
.append(param2)
.append(param3)
.append(param4)
.toString()
}
在kotlin中调用:
第二个参数没有默认值,则调用时必须有一个参数
joinToString(
"aaa"
, param3 =
"ccc"
, param2 =
"ddd"
)
joinToString(
"aaa"
,
"bbb"
,
"ccc"
,
"ddd"
)
joinToString(
"aaa"
,
"ccc"
,
"ddd"
)
joinToString(param2 =
"aaa"
)
在java中调用:
在java与kotlin的混合项目中,会发现用kotlin实现的带默认参数的函数,在java中去调用的化就不能利用这个特性了,还是需要给所有参数赋值
这时候可以在kotlin的函数前添加注解@JvmOverloads,添加注解后翻译为class的时候kotlin会帮你去生成多个函数实现函数重载,
添加@JvmOverloads后,反编译后可以看到帮助我们生成多个参数的重载函数,
然后在java中也可以如下调用,
注意:
- 原函数第二个参数没有默认值,因此这里如果只传一个参数则默认是第二个参数:
- java中无法指定参数位置
在java中要声明一个model类需要实现很多的代码,首先需要将变量声明为private,然后需要实现get和set方法,还要实现对应的hashcode equals toString方法等
如果用kotlin,只需要一行代码就可以做到。
/**
* Kotlin会为类的参数自动实现get set方法
* */
class
User(val name: String, val age: Int, val gender: Int, var address: String)
/**
* 用data关键词来声明一个数据类,除了会自动实现get set,还会自动生成equals hashcode toString
* */
data
class
User2(val name: String, val age: Int, val gender: Int, var address: String)
对于Kotlin中的类,会为它的参数自动实现get set方法。而如果加上data关键字,还会自动生成equals hashcode toString。原理其实数据类中的大部分代码都是模版代码,Kotlin聪明的将这个模版代码的实现放在了编译器处理的阶段。
坑点:
data class使用fastJson转换数据时会报如下错误,fastjson反射获取无参构造函数,而data class至少有一个有参构造函数,
com.alibaba.fastjson.JSONException:
default
constructor not found.
class
cn.ac.ia.iot.www.telemedicine.mvp.model.bean.DataClass
at com.alibaba.fastjson.parser.JavaBeanInfo.build(JavaBeanInfo.java:
496
)
at com.alibaba.fastjson.parser.JavaBeanDeserializer.(JavaBeanDeserializer.java:
35
)
at com.alibaba.fastjson.parser.ParserConfig.getDeserializer(ParserConfig.java:
229
)
at com.alibaba.fastjson.parser.ParserConfig.getDeserializer(ParserConfig.java:
148
)
at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:
683
)
at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:
659
)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:
238
)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:
210
)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:
169
)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:
278
)
解决方案:
1、无法使用dataclass,使用普通class即可,手写toString
2、替换为Gson等其他库
3、使用fastJson某一低版本、所有参数添加默认值生成无参构造等方式(试了生成了无参构造,但仍报错)
本以为是data class 的锅,没想到是FastJson的锅 · 大专栏
Kotlin 遇到 JSONException - 简书
四、扩展函数 4-1、自定义的扩展函数例一:如String类中,我们想获取最后一个字符,String中没有这样的直接函数,你可以用.后声明这样一个扩展函数:
//扩展函数
fun String.lastChar(): Char =
this
.get(
this
.length -
1
)
//使用
fun testFunExtension() {
val str =
"test extension fun"
;
println(str.lastChar())
}
这样定义好lastChar()函数后,之后只需要import进来后,就可以用String类直接调用该函数了,跟调用它自己的方法没有区别。这样可以避免重复代码和一些静态工具类,而且代码更加简洁明了。
例二:协程和视图生命周期相绑定
若协程用于异步加载一张图片,这张图片显示在 ImageView 上。当 ImageView 所在界面已被销毁时,得及时取消协程的加载任务,以释放资源:
fun Job.autoDispose(view: View) {
val isAttached = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && view.isAttachedToWindow || view.windowToken !=
null
if
(!isAttached){
cancel()
}
// 监听视图生命周期
val listener = object : View.OnAttachStateChangeListener {
// 在视图生命周期结束时取消协程
override fun onViewDetachedFromWindow(v: View?) {
cancel()
v?.removeOnAttachStateChangeListener(
this
)
}
override fun onViewAttachedToWindow(v: View?) = Unit
}
view.addOnAttachStateChangeListener(listener)
invokeOnCompletion {
view.removeOnAttachStateChangeListener(listener)
}
}
为Job扩展方式,传入 View 类型的参数,表示该Job和该 View 的生命周期绑定,当 View 生命周期结束时,自动取消协程。
4-2、Kotlin的源码中定义的常用的扩展函数also、apply、let、runapply函数的作用是:调用某对象的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和延迟初始化lateinit5-1 懒初始化by lazy
懒初始化是指推迟一个变量的初始化时机,变量在使用的时候才去实例化,这样会更加的高效。因为我们通常会遇到这样的情况,一个变量直到使用时才需要被初始化,或者仅仅是它的初始化依赖于某些无法立即获得的上下文。
5-2 延迟初始化lateinit
另外,对于var的变量,如果类型是非空的,是必须初始化的,不然编译不通过,这时候需要用到lateinit延迟初始化,使用的时候再去实例化。
5-3 by lazy 和 lateinit 的区别
-
by lazy 修饰val的变量
-
lateinit 修饰var的变量,且变量是非空的类型