函数 & 方法
- 类中的函数为方法;有receiver的函数即为方法;类是方法的receiver;
- 函数返回值为空时,为
Unit
可以省略; - 函数的类型
foo() {} () -> Unit
foo(p0: Int): String (Int) -> String
class Foo {
fun bar(p0: String, p1: Long): Any{...}
}
Foo.(String, Long) -> Any
(Foo, String, Long) -> Any
- 函数引用
fun foo() {} ::foo
fun foo(p0: Int): String {} ::foo
class Foo {
fun bar(p0: String, p1: Long): Any{...}
}
Foo:bar
基础类定义
// 类默认是public,其中方法,变量 默认public
- 类基础定义
class SimpleClass {
var x: Int = 0 // 必须初始化
}
- 类构造方法1
class SimpleClass {
var x: Int = 0
constructor(x: Int) {
this.x = x
}
}
- 类构造方法2
class SimpleClass
constructor(x: Int){
var x: Int = x
}
- 类构造方法3
class SimpleClass(x: Int){
var x: Int = x
}
- 类构造方法4
// 有 var 或 val 表示成为类的属性
class SimpleClass(var x: Int){
}
- 接口实现
- 接口中可以有成员变量
- 接口类中只能有行为不能有状态;
interface SimpleInf {
val simpleProperty: Int // property
fun simpleMethod()
}
class SimpleClass(var x: Int)
:SimpleInf {
override val simpleProperty: Int
get() {
return 2
}
// override 实现接口时必须写;
override fun simpleMethod() {
}
}
- 抽象类的定义
class AbsClass {
abstruct fun absMethod()
// 可被复写的方法,如果允许被复写,必须加 open 关键字;
open fun overridable() {}
// 不可被复写的方法
fun nonOverridable() {}
}
- 类的继承
继承一个普通类A时,这个普通类A前面需要加open
关键字,表示允许其他类继承;普通类A中复写的方法默认可以被子类复写,可以在类前加final
关键字禁止子类复写该方法;
// 继承类时,需要传构造方法参数,无参数也要有();
class SimpleClass(var x: Int)
: AbsClass(), SimpleInf {
...
}
- 成员变量的get/set
class Person(arg: Int, name: String) {
// get 和 set 和变量是一体的;
var arg: Int = age
get() {
return field
}
set(value) {
field = value
}
var name: String = name
}
- 属性的引用
val ageRef = Persion::age
var persion = Persion(18, lily)
var nameRef = persion::name
nameRef.set("jojo")
nameRef.get()
类的构造器
init
块- 属于类构造器的函数体,
init
块中可以访问构造方法的参数; - 类中属性必须初始化;
- 属于类构造器的函数体,
- 副构造器
// 类名后为主构造器
class Person(var age: Int, var name: String) {
// 副构造器一定要调用主构造器;
constructor(age: Int): this(age, "unknown") {
}
}
- 不定义主构造器(不推荐使用)
class Person {
var age: Int
var name: String
// super() 无参可以省略;
constructor(age: Int, name: String): super() {
this.age = age
this.name = name
}
}
- 主构造器默认参数(推荐)
class Person(var age: Int, var name:String = "unknown") {
}
当有默认参数时并且需要在 java
类使用的话需要加 @JvmOverloads
注解;
@JvmOverloads
在函数有默认参数时添加,使 java
类可以使用;
class Person
@JvmOverloads
constructor(var age: Int, var name:String = "unknown") {
}
- 构造同名的工厂函数
类有时不是自己创建的,可以使用构造同名工厂函数创建满足需求的类;
fun Person(name: String): Person {
...
}
fun main() {
// String构造方法
var str = String()
// 使用构造同名的工厂函数
var str1 = String(charArrayOf('1', '2'))
}
类的成员可见性
模块:大致可以认为是一个Jar包、一个aar;
- 可见性对比
可见性类型 | Java | Kotlin |
---|---|---|
public | 公开 | 与Java相同,默认 |
internal | x | 模块内可见 |
default | 包内可见 | x |
protected | 包内及子类可见 | 类内及子类可见 |
private | 类内可见 | 类或文件内可见 |
- 修饰对象
kotlin
默认为public
可见性类型 | 顶级声明 | 类 | 成员 |
---|---|---|---|
public | Y | Y | Y |
internal | Y, 模块 | Y, 模块 | Y, 模块 |
protected | N | N | Y |
private | Y, 文件 | Y, 文件 | Y, 类 |
internal
vsdefault
- 一般由SDK或公共组价开发者用于隐藏模块内部细节实现;
default
可通过外部创建相同名来访问,访问控制非常弱;default
会导致不同抽象层次的类剧集到相同的包之下;internal
可以方便处理内外隔离,提升模块代码内聚减少接口暴露;internal
修饰的Kotlin
类成员在Java
当中可直接访问;
- 属性可见性
// 1. 构造函数私有化
class Person
private constructor(var age: Int, var name: String) {
}
// 2. 私有化属性
// 私有化属性,外部无法访问
class Person(private var age: Int, var name: String) {
private var lastName: String = ""
var firstName: String = ""
private set //私有化属性firstName的setter,外部只能读取;
// private get // 不能这样写,getter的可见性必须与属性的可见性一致;
}
- 顶级声明的可见性
- 顶级声明指文件内直接定义的属性、函数、类等;
- 顶级声明不支持
protected
; - 顶级声明被
private
修饰表示文件内部可见;
类属性的延迟初始化
有些时候类的属性不能在创建是立刻初始化;例如Android中UI控件,需要在onCreate()之后才能赋值;
- 设置属性为可空类型,先赋值了
null
; 不推荐 - 属性前添加
lateinit
关键字;不推荐- lateinit 会让编译器忽略变量的初始化,不支持
Int
等基本类型; - 开发者必须能够完全确定变量值得声明周期下使用
lateinit
; - 不要在复杂的类中使用
lateinit
,它只会让你的代码更加脆弱; - Kotlin 1.2 加入的判断
lateinit
属性是否初始化的API最好不要用;
- lateinit 会让编译器忽略变量的初始化,不支持
- 使用
lazy
推荐
初始化与声明内聚;无需声明可空类型;
// 只要在 nameView 首次被访问时执行
private val nameView by lazy {
findViewById<TextView>(R.id.textview)
}
空类型安全
- 定义可以为空的变量
var x: String? = "hi"
x = null // 运行赋值为空
- 空类型中使用
elvis
运算符
String
是String?
的子类;
var x: String? = "hi"
// 不使用elvis运算符,lenght变量类型为Int?
var length: Int? = x?.length
// 使用elvis运算符,lenght变量类型为Int;
var length_1: Int = x?.lenght ?: 0
- kotlin调用java代码
String!
是指java
虚拟机的平台类型;平台类型客观存在,不能主观定义;- 需要根据平台代码自行确定是否为可空类型;
kotlin类型转换
- 基础用法
if (p is Person) {
println(p.name);
}
- 作用范围
如果变量为全局变量,if判断不为空语句块中,变量类型不会转为非空类型;
var value: String? = null
value = "hi"
if (value != null) {
// if 判断不为空的语句块中,value是 String 类型;
println(value.length)
}
// if 判断不为空的语句块外,value还是 String? 类型
- 类型安全转换
val kotliner: Kotliner = ...
println((kotliner as? Person)?.name)
- 使用建议
- 尽量使用
val
来声明不可变引用,让程序的含义更加清晰确定; - 尽可能减少函数对外部变量的访问,也为函数式编程提供基础;
- 必要时创建局部变量指向外部变量,避免因它变化引起程序错误;
- 尽量使用
- kotlin使用类做参数时写法
Java
Person.class
Kotlin
Person::class.java
常量和变量
var
变量val
严格讲为只读变量、通常作为常量使用,为运行时常量;(读取值不一定相同)const val
为编译时常量;
// 读取 b 属性时,值不一定相同;
class X {
val b: Int
get() {
return (Math.random() * 100).toInt()
}
}
- 常量值
使用const
关键字的要求:- 只能定义在全局范围;
- 只能修饰基本类型;
- 必须立即用字面量初始化;
// 对标 Java static final int
const val b = 3
- 常量引用
自定义类对象的引用即为常量引用;
运算符与中缀表达式
- 自定义运算符演示
operator fun String.times(right: Int): String {
return (1..right).joinToString("") { this }
}
fun main() {
var star = "*"
println(star * 5)
}
// 输出结果为 *****
- 中缀表达式 函数前使用
infix
关键字
infix fun String.rotate(count: Int): String {
val index = count % length
return this.substring(index) + this.substring(0, index)
}
println("Hello" rotate 3)
lambda 表达式
- lambda 表达式是匿名函数的语法糖
- lambda表达式的集中写法
val f1: (Int) -> Unit = { p: Int ->
println(p)
}
val f1: Function1<Int, Unit> = { p: Int ->
println(p)
}
val f1: Function1<Int, Unit> = { p ->
println(p)
}
val f1 = { p: Int ->
println(p)
}
// 只有一个参数时
val f1: Function<Int, Unit> = {
println(it)
}
高阶函数
高阶函数是指参数类型包含函数类型或返回值类型为函数类型。
- 函数类型作为最后一个参数可移动到括号外变;
- 只有一个lambda表达式时,可以省略小括号;
- 只要一个参数的lambda表达式,表达式中形参默认为
it
; - 基础样例
fun cost(block: () -> Unit) {
val start = System.currentTimeMillis()
block()
println("${System.currentTimeMillis() - start}ms")
}
fun fibonacci(): () -> Long {
var first = 0L
var second = 1L
return {
val next = first + second
val current = first
first = second
second = next
current
}
}
fun main() {
cost {
val fibonacciNex = fibonacci()
for (i in 1..10) {
println(fibonacciNex())
}
}
}
内联函数
- 在函数前加
inline
表示内联函数; - 内联函数更适用于高阶函数;
- 函数本身被内联到调用处;
- 函数的函数参数被内联到调用处;
- 内联函数减少了函数的调用,对系统开销优化;
- 内联高阶函数的
return
return@forEach
指跳出这一次内联函数的调用,等价于continue
;
val ints = intArrayOf(1, 2, 3,4) ints.forEach { if (it == 3) return@forEach println("Hello $it") } /* 输出结果 Hello 1 Hello 2 Hello 4 */
non-local return
从外部函数返回
fun main() { nonLocalReturn { return // 从main函数返回 } }
- 不是所有内联函数都可以
non-local return
;函数在内联函数调用处与定义处不在同一个上下文,有可能存在不合法的non-local return
;- 禁止
non-local return
;在内联函数的形参函数前加crossinline
关键字; - 还可以使用
noinline
关键字,禁止内联;对性能没有改善,不推荐使用;
- 禁止
- 内联属性 ?
- 内联函数的限制
public/protected
的内联方法只能访问对应类的public
成员;- 内联函数的内联函数参数(没有标记
noinline
)不能被存储(赋值给变量); - 内联函数的内联函数参数(没有标记
noinline
)只能传递给其他内联函数参数;
有用的高级函数
let
// 推荐,返回表达式的结果
var r = X.let { x -> R}
run
// 返回表达式的结果
var r = X.run {this: X -> R}
also
// 推荐,返回Receiver
var x = X.also { x -> Unit}
apply
// 返回Receiver
var x = X.also {this: X -> Unit}
use
// 推荐,自动关闭资源
var r = Closeable.use { c -> R}
集合变换与序列
filter
&map
filter
:保留满足条件的元素;
map
:集合中的所有元素一一映射到其他元素构成新集合;
asSequence()
:转为懒序列,sine1.1;
forEach
:集合变换的开关;
fun main() {
var list = listOf(1, 2, 3, 4, 5)
list.asSequence()
.filter {
println("filter:$it")
it % 2 == 0
}
.map {
println("map:$it")
"=${it * 2 + 1}="
}
.forEach { println("forEach: $it") }
println("list:$list")
}
- flatMap 变换
集合中的所有元素一一映射到新的集合并合并这些集合得到新的集合;
???
- 集合的聚合操作
- sum 所有元素求和
- reduce 将元素一次按规则聚合,结果与元素类型一致;
- 给定初始值,将元素按规则聚合,结果与初始值类型一致;
SAM 转换
Java | Kotlin | |
---|---|---|
Java 接口 | 支持 | 支持 |
Kotlin 接口 | 支持 | 不支持 |
Java 方法 | 支持 | 支持 |
Kotlin 函数(方法) | 支持 | 不支持 |
抽象类 | 不支持 | 不支持 |
代理 Delegate
- 接口代理
interface Api {
fun a()
fun b()
fun c()
}
class ApiImpl: Api {
override fun a() {}
override fun b() {}
override fun c() {}
}
// 传统写法
class ApiWrapper(val api: Api): Api {
override fun a() { api.a() }
override fun a() { api.b() }
override fun a() { api.c() }
}
// 使用接口代理
// 对象 api 代替类 ApiWrapper 实现接口 Api ;
// 对于对象 api 的唯一邀请就是实现被代理的接口;
class ApiWrapper(val api: Api)
: Api by api {
override fun a() {
println("c is called.")
api.c()
}
}
- 属性代理
lazy
: 代理了val
属性的getter
方法;observable
: 代理了getter
和setter
方法;
observable
方法创建了一个ObservableProperty
对象;ObservableProperty
实现了getter' 和
setter` 方法;
fun main() { val stateObservable = Delegates.observable(0) { onChange, oldView, newValue -> println("state oldView:$oldView newValue:$newValue") } var state: Int by stateObservable state = 3; state = 4; }
vetoable
的使用场景及原理 ???- 属性代理在简化配置读写方面的作用 ???
- 分析
notNull
的使用方法并与lateinit
做对比 ??? - 使用属性代理实现
SharedPreferences
的读写 ???
单例 object
object
的定义
// java 饿汉式单例;
public class Singleton {
public static final Singleton INSTANCE = new Singleton();
}
// kotlin 饿汉式单例,等效于 java 饿汉式单例;
// 类加载时实例化对象 Singleton 既是类名也是对象名;
object Singleton {
// @JvmField 指在 java 中 x 为静态变量,没有 get 和 set;
@JvmField c var x: Int = 2
// @JvmStatic 在 java 中表示 static 关键字,需要静态方法是使用,不能在普通类中使用;
@JvmStati fun y() {...}
}
// 访问 object 的成员
Singleton.x
Singleton.y()
- 普通类中 Java 静态成员
// java 中实现
public class Foo {
@JvmField var x: Int = 2 // 生成非静态Field
public static void y() {}
}
// kotlin 中实现
class Foo {
// 伴生对象,只能定义一个
companion object {
@JvmField var x: Int = 2 // 生成静态Field
@JvmStatic fun y() {}
}
}
object
的构造器有系统生成默认无参的方法了,不能自己定义,可以使用init
块初始化;object
的类继承和普通了一致;
内部类
- 内部类的定义
// java 实现
public class Outer {
class Inner {}
static class StaticInner {}
}
// koltin 实现
class Outer {
// 非静态内部类,实例持有外部类实例引用
inner class Inner
// 静态内部类
class Inner
}
// kotlin 初始化
val inner = Outer().Inner()
val staticInner = Outer.StaticInner()
- 内部
object
内部objcet
不存在非静态的情况,不可用inner
修饰;
object OuterObject {
object StaticInnerObject
}
- 匿名内部类
- 匿名内部类基础实现
object: Runable { override fun run() {...} }
- 匿名内部类实现多个接口
object: Cloneable, Runnable { override fun run() {...} }
data class
数据类
在 class
前加 data
关键字;
- 数据量定于与使用
// 定义在主构造器中的属性又称为 component ;
// 编译器基于 comonent 自动生成了 equals/hashCode/toString/copy (浅拷贝) ;
data class Book(val id: Long, val name: String)
// 访问
val id = book.component1()
val name = book.component2()
val (id, name) = book
JavaBean
vsdata class
JavaBean | data class | |
---|---|---|
构造方法 | 默认无参构造 | 属性作为参数 |
字段 | 字段私有,Getter /Setter 公开 | 属性 |
继承性 | 可继承也可被继承 | 不可被继承 |
component | 无 | 相等性、解构等 |
- 合理使用
data class
- 属性类型最好为基本类型、
String
、其他Data Class
; - 最好不要自定义属性的
Getter-Setter
; - 属性最好是不可变的(
val
);
- 属性类型最好为基本类型、
枚举类
- 定义
// java
enum State {
Idle, Busy
}
// kotlin
enum class State {
Idle, Busy
}
// 使用
State.Idle.name // Idle
State.Idle.ordinal // 0
- 定义构造器
enum class State(val id: Int) {
Idle(0), Busy(1)
}
- 枚举类实现接口
枚举的父类是Enum
,所以不能继承其他类;- 统一实现
enum class State: Runnable { Idle, Busy override fun run() { println("For every state.") } }
- 各自实现
enum class State: Runnable { Idle { override fun run() {...} },Busy {...}; }
- 枚举类定义扩展
fun State.next(): State {
State.values().let {
val nextOrdinal = (oridinal + 1) % it.size
it[nextOrdinal]
}
}
- 条件分支
val value = when(state) {
State.Idle -> {0}
State.Busy -> {1}
}
- 枚举比较
- 区间
enum class Color {
White, Red, Green, Blue, Yellow
}
val colorRange = Color.White..Color.Green
val color = Color.Blue
color in colorRange // false
密封类
关键字 sealed
,在 class
之前;
- 概念
- 密封类是一种特殊的抽象类;
- 密封类的子类定义在与自身相同的文件中;
- 密封类的子类的个数是有限的;
内联类
关键字 inline
,在 class
之前;
1.概念
1. 内联类是对某一个类型的包装
2. 内联类是类似于 Java
装箱类的一种类型
3. 编译器会尽可能使用被包装的类型进行优化;
4. 内联类在 1.3 中处于公测阶段,谨慎使用;
数据类的 json
序列化
- Gson
反序列化不能识别默认参数; - Moshi
- Kotlinx.serialization
- 框架对比
Gson | Moshi | K.S | |
---|---|---|---|
空类型 | 否 | 反射、注解 | 是 |
默认值 | 否 | 反射、注解 | 是 |
init 块 | NoArg 插件 | 反射、注解 | 是 |
Java 类 | 是 | 是 | 否 |
跨平台 | 否 | 否 | 否 |
泛型
-
泛型的基本声明方法
函数声明泛型fun <T> maxOf(a: T, b: T): T
类声明泛型
class List<T>
使用
// maxOf<String> 中 String 为泛型实参; val max: String = maxOf<String>("AAA", "BBB") // 自动类型推导 val max: String = maxOf("AAA", "BBB")
-
多个约束
fun <T> callMax(a:T, b:T) where T: Comparable<T>, T: () -> Unit { if(a > b) a() else b() }
-
泛型的型变
- 不变
- 协变 关键字 out // 生产者,一般为方法的返回值;
- 逆变 关键字 in // 消费者,一般为函数的参数类型;
open class Waste class DryWaste: Waste() class Dustbin<T: Waste> { // 这里的 T 为逆变点 fun put(t: T) { } } fun main() { val dustbin: Dustbin<Waste> = Dustbin() val dryWasteDustbin: Dustbin<DryWaste> = dustbin val waste: Waste = Waste() val dryWaste: DryWaste = DryWaste() dustbin.put(waste) dustbin.put(dryWaste) // dryWasteDustbin.put(waste) dryWasteDustbin.put(dryWaste) }
- 个人总结
协变和逆变使泛型有继承关系的类具有继承关系;
-
星投影
- ‘*’ 可用在变量类型声明的位置;
- ‘*’ 可以描述一个未知的类型;
- ‘*’ 所替换的类型在:
- 协变点返回泛型参数上限类型;
- 逆变点接受泛型参数下限类型;
- ‘*’ 不能直接或间接应用在属性或函数上,以下为错误示范:
QueryMap<String, *>()
maxOf<*>(1, 3)
- ‘*’ 适用于作为类型的描述的场景,以下为正确示范:
val queryMap: QueryMap<*, *>
if(f is Function<*, *>){...}
HashMap<String, List<*>>()
-
内联特化
inline fun <reified T> genericMethod(t: T) {
//val t = T() // 报错,不允许
val ts = Array<T>(3) {...}
val jclass = T::class.java
val list = ArrayList<T>()
}
反射
- 使用kotlin反射需要引入反射依赖
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" implementation "org.jetbrains.kotlin:kotlin-reflect"
- 基本数据结构
KType
:描述未参数的类型或反向参数等,例如Map<String, Int>
;可通过typeOf
或者以下类型获取对应的父类、属性、函数参数等;KClass
:描述对象的实际类型,不包含泛型参数,例如Map
;可通过对象、类型名直接获取KProperty
:描述属性,可通过属性引用,属性所在类的KClass
获取;KFunction
:描述函数,可通过函数易用、函数所在类的KClass
获取;
- 反射数据结构:
Kotlin
vsJava
Kotlin | Java |
---|---|
KType | Type |
KClass | class |
KProperty | Field |
KFunction | Method |
Kotlin
中使用反射Java
反射- 优点:无需引入额外依赖,首次使用速度相对较快
- 缺点:无法访问
Kotlin
语法特性,需对Kotlin
生成的字节码足够了解;
Kotlin
反射
- 优点:支持访问
Kotlin
几乎所有特性,API设计更友好; - 缺点:引入
Kotlin
反射库(2.5MB, 编译后400KB),首次调用慢;
Kotlin
|Java
浅复制和深复制- 深复制案例 |
Model
映射阿案例
协程
-
作用
- 协程可以让异步代码同步化;
- 协程可以降低异步程序的设计复杂度;
- 挂起和恢复可以控制执行流程的转移;
- 同步代码比异步代码更灵活,更容易实现复杂业物;
-
线程 vs 协程
线程Thread: 指操作系统的线程,也称为内核线程;
协程Coroutine: 指语言实现的协程,运行在内核线程之上; -
协程的分类
- 按调用栈
- 有栈协程
- 无栈协程
- 按调用关系
- 对称协程:调度权可以转移给任意协程,协程之间是对等关系;
- 非对称协程:调度只能转移给调用自己的协程,协程存在父子关系;
- 按调用栈
-
kotlin
协程的基本要素- 挂起函数。关键字
suspend
写在fun
之前; - 调用挂起函数的位置,表示挂起点;
- 挂起函数只能在其他挂起函数或协程中调用;
- 挂起函数调用是包含了协程"挂起"的语义;
- 挂起函数返回时则包含了协程"恢复"的语义;
kotlin
是无栈协程- 挂起函数的类型
// 函数类型: suspend () -> Unit suspend fun foo() {} // suspend 修饰的函数展开后为以下样子; fun foo(continuation: Continuation<Unit>): Any{} // 函数类型: suspend (Int) -> String suspend fun bar(a: Int): String { return "Hello" } // Any返回类型: 没有真正挂起时,返回Continuation中传的泛型,如果真正挂起返回挂起标志对象: COROUTINE_SUSPENDED; fun bar(a: Int, continuation: Continuation<String>): Any {...}
- suspend 函数中,只有切线程才会真正挂起,
Continuation
;
- 挂起函数。关键字
-
将回调转写成挂起函数
- 使用
suspendCoroutine
获取挂起函数的Continuation
; - 回调成功的分支使用
Continuation.resume(value)
; - 回调失败则使用
Continuation.resumeWithException(e)
;
- 使用
-
创建协程
fun <T> (suspend () -> T).createCoroutine( completion: Continuation<T>): Continuation<Unit> fun <R, T> (suspend R.() -> T).createCoroutine(receiver: R, completion: Continuation<T>): Continuation<Unit>
suspend
函数本事执行需要一个Continuation
实例在恢复时调用,即此处的参数:completion
;- 返回值
Continuation<Unit>
则是创建出来的协程的载体,receiver suspend
函数会被传给该实例作为协程的实际执行体;
-
协程上下文
-
协程执行过程中需要携带数据
-
索引是
CoroutineContext.Key
; -
元素是
Coroutine Context.Element
; -
拦截器
- 拦截器
ContinuationInterceptor
是一类协程上下文元素; - 可以对协程上下文所在协程的
Continuation
进行拦截;
interface ContinuationInterceptor: CoroutineContext.Element { fun <T> interceptContinuation( continuation: Continuation<T>): Continuation<T> ... }
- 拦截器
-
Continuation
执行示意
当挂起函数suspend{...}
中包含挂起函数a()
时,a() 使用suspendCoroutine
api 获取suspend{...}
的continuatian
并挂起,然后执行挂起函数a()
; 在a()
中或其他位置调用continuatian.resume(...)
重新恢复suspend{...}
的执行;suspend { a() }.startCoroutine(...) suspend fun a() = suspendCoroutine<Unit> { thread { it.resume(Unit) } }
- 其中
it
表示SafeContinuation
; - SafeContinuation 的作用就是确保:
resume
只被调动一次- 如果在当前线程调用栈上直接调用则不会挂起;
- 其中
-
拦截
Continuation
suspend { a() }.startCoroutine(...)
SafeContinuation
仅在挂起点时出现;- 拦截器在每次(恢复)执行协程体时调用;
SuspendLambda
是协程函数体;
-
Kotlin 协程框架
Kotlin
协程的启动模式
启动模式 | 功能特性 |
---|---|
DEFAULT | 立即开始调度协程体,调度前若取消则直接取消 |
ATOMIC | 立即开始调度,且在第一个挂起点前不能被取消 |
LAZY | 只要在需要(start /join /await )时开始调度 |
UNDISPATCHED | 立即在当前线程执行协程体,直到遇到第一个挂起点(后面取决于调度器) |
Kotlin
协程的调度器
调度器 | Java Vm | Native |
---|---|---|
Default | 线程池 | 主线程循环 |
Main | UI线程 | 同 Default |
Unconfined | 直接执行 | 直接执行 |
IO | 线程池 | – |
-
其他特性
Channel
: "热"数据流,并发安全的通信机制;Flow
: "冷"数据流,协程的响应式 API;Select
: 可对多个挂起事件进行等待;
-
作用域对异常传播的影响
类型 | 生产方式 | 异常传播特征 |
---|---|---|
顶级 | 通过 GlobalScope 创建 | 不向外部传播 |
协同 | Job 嵌套、coroutineScope 创建 | 双向传播 |
主从 | 通过supervisorScope 创建,与内部直接子协程主从,与外部协同 | 自上而下 单向传播 |
-
异常处理器对
async
是没有意义的;await
调用是抛出该异常,一会取消外部作用域; -
android
项目协程框架依赖配置
最新版本在:https://github.com/Kotlin/kotlinx.coroutinesimplementation "org.jetbrains.kotlin:kotlinx-coroutines-android:1.3.8"
Channel
-
Channel
的关闭- 调用
close
关闭Channel
; - 关闭后调用
send
抛异常,isClosedForSend
返回true
; - 关闭后调用
receive
可接受缓存的数据; - 缓存消费完后
receive
抛异常,isClosedForReceive
返回true
;
- 调用
-
Channel
的迭代hasNext
在有缓存的数据时范湖true
;hasNext
在未关闭且缓存为空时挂起;hasNext
在正常关闭且缓存为空时返回false
;
for (i in channel) { log("received:", i) }
-
Channel
的协程Builder
produce
:启动一个生产者协程,返回ReceiveChannel
;- 通过
Builder
启动的协程结束后自动关闭对应的Channel
;
-
BroadcastChannel
Channel
的元素只能被一个消费者消费;BroadcastChannel
的元素会分发给所有的订阅者;BroadcastChannel
不支持RENDEZVOUS
;
Flow
- 使用
Flow
GlobalScope.launch(dispatcher) { val intFlow = flow{ // 运行在 flowOn 指定线程 emit(1) delay(100) emit(2) emit(3) } } intFlow.flowOn(Dispatchers.IO) intFlow.collect{ // 在 flow 中每次调用 emit() 都会调用该方法 // 运行在flow所在调度器指定线程 (dispatcher) log(it) }
- 异常处理
flow { emit(1) throw ArithmeticException("Div 0") }.catch{t: Throwable -> log("caught error: $t") }.onCompletion{t: Throwable? -> // 这里的t是指上吗的catch未捕获的异常 log("finally") }
Flow
的取消Flow
的运行依赖于协程;Flow
的取消取决于collect
所在协程的取消;collect
作为挂起函数可以响应所在协程的取消状态;
- 从集合创建
Flow
listOf(1,2,3,4).asFlow() setOf(1,2,3,4).asFlow() flowOf(1,2,3,4)
- 从
Channel
创建Flow
val channel = Channel<int>() channel.consumeAsFlow()
Flow
元素并发生成
Flow
不是线程安全的,应使用channelFlow
;channelFlow { send(1) withContext(Dispatchers.ID) { send(2) } }
Back Pressure
指消费者速度慢与生成者速度;buffer
: 指定固定容量的缓存;conflate
: 保留最新的指;colletLatest
: 新值发送时取消之前的;
flow { emit(1) delay(50) emit(2) }.collectLatest{value -> println("Collectin $value") delay(100) println("$vale collected") }