kotlin学习笔记

函数 & 方法
  1. 类中的函数为方法;有receiver的函数即为方法;类是方法的receiver;
  2. 函数返回值为空时,为 Unit 可以省略;
  3. 函数的类型
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
  1. 函数引用
fun foo() {}     ::foo
fun foo(p0: Int): String {}    ::foo
class Foo {
    fun bar(p0: String, p1: Long): Any{...}
}
Foo:bar
基础类定义

// 类默认是public,其中方法,变量 默认public

  1. 类基础定义
class SimpleClass {
    var x: Int = 0 // 必须初始化
}
  1. 类构造方法1
class SimpleClass {
    var x: Int = 0
    constructor(x: Int) {
        this.x = x
    }
}
  1. 类构造方法2
class SimpleClass 
        constructor(x: Int){
    var x: Int = x
}
  1. 类构造方法3
class SimpleClass(x: Int){
    var x: Int = x
}
  1. 类构造方法4
// 有 var 或 val 表示成为类的属性
class SimpleClass(var x: Int){

}
  1. 接口实现
    1. 接口中可以有成员变量
    2. 接口类中只能有行为不能有状态;
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() {
   }
   
}
  1. 抽象类的定义
class AbsClass {
   
   abstruct fun absMethod()
   // 可被复写的方法,如果允许被复写,必须加 open 关键字;
   open fun overridable() {}
   // 不可被复写的方法
   fun nonOverridable() {}
   
}

  1. 类的继承
    继承一个普通类A时,这个普通类A前面需要加 open 关键字,表示允许其他类继承;普通类A中复写的方法默认可以被子类复写,可以在类前加 final 关键字禁止子类复写该方法;
// 继承类时,需要传构造方法参数,无参数也要有();
class SimpleClass(var x: Int)
    : AbsClass(), SimpleInf {
   ...
}
  1. 成员变量的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
}
  1. 属性的引用
val ageRef = Persion::age
var persion = Persion(18, lily)
var nameRef = persion::name
nameRef.set("jojo")
nameRef.get()
类的构造器
  1. init
    1. 属于类构造器的函数体, init 块中可以访问构造方法的参数;
    2. 类中属性必须初始化;
  2. 副构造器
// 类名后为主构造器
class Person(var age: Int, var name: String) {
    // 副构造器一定要调用主构造器;
    constructor(age: Int): this(age, "unknown") {
    
    }
}
  1. 不定义主构造器(不推荐使用)
class Person {
    var age: Int
    var name: String
    // super() 无参可以省略;
    constructor(age: Int, name: String): super() {
        this.age = age
        this.name = name
    }
}
  1. 主构造器默认参数(推荐)
class Person(var age: Int, var name:String = "unknown") {

}

当有默认参数时并且需要在 java 类使用的话需要加 @JvmOverloads 注解;
@JvmOverloads 在函数有默认参数时添加,使 java 类可以使用;

class Person
@JvmOverloads
constructor(var age: Int, var name:String = "unknown") {

}
  1. 构造同名的工厂函数
    类有时不是自己创建的,可以使用构造同名工厂函数创建满足需求的类;
fun Person(name: String): Person {
    ...
}

fun main() {
    // String构造方法
    var str = String() 
    // 使用构造同名的工厂函数
    var str1 = String(charArrayOf('1', '2'))
}
类的成员可见性

模块:大致可以认为是一个Jar包、一个aar;

  1. 可见性对比
可见性类型JavaKotlin
public公开与Java相同,默认
internalx模块内可见
default包内可见x
protected包内及子类可见类内及子类可见
private类内可见类或文件内可见
  1. 修饰对象
    kotlin 默认为 public
可见性类型顶级声明成员
publicYYY
internalY, 模块Y, 模块Y, 模块
protectedNNY
privateY, 文件Y, 文件Y, 类
  1. internal vs default
    1. 一般由SDK或公共组价开发者用于隐藏模块内部细节实现;
    2. default 可通过外部创建相同名来访问,访问控制非常弱;
    3. default 会导致不同抽象层次的类剧集到相同的包之下;
    4. internal 可以方便处理内外隔离,提升模块代码内聚减少接口暴露;
    5. internal 修饰的 Kotlin 类成员在 Java 当中可直接访问;
  2. 属性可见性
// 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的可见性必须与属性的可见性一致;
}
  1. 顶级声明的可见性
    1. 顶级声明指文件内直接定义的属性、函数、类等;
    2. 顶级声明不支持 protected ;
    3. 顶级声明被 private 修饰表示文件内部可见;
类属性的延迟初始化

有些时候类的属性不能在创建是立刻初始化;例如Android中UI控件,需要在onCreate()之后才能赋值;

  1. 设置属性为可空类型,先赋值了 null; 不推荐
  2. 属性前添加 lateinit 关键字;不推荐
    1. lateinit 会让编译器忽略变量的初始化,不支持 Int 等基本类型;
    2. 开发者必须能够完全确定变量值得声明周期下使用 lateinit
    3. 不要在复杂的类中使用 lateinit,它只会让你的代码更加脆弱;
    4. Kotlin 1.2 加入的判断 lateinit 属性是否初始化的API最好不要用;
  3. 使用 lazy 推荐
    初始化与声明内聚;无需声明可空类型;
// 只要在 nameView 首次被访问时执行
private val nameView by lazy {
    findViewById<TextView>(R.id.textview)
}
空类型安全
  1. 定义可以为空的变量
var x: String? = "hi"
x = null // 运行赋值为空
  1. 空类型中使用 elvis 运算符
    StringString? 的子类;
var x: String? = "hi"
// 不使用elvis运算符,lenght变量类型为Int?
var length: Int? = x?.length
// 使用elvis运算符,lenght变量类型为Int;
var length_1: Int = x?.lenght ?: 0 
  1. kotlin调用java代码
    1. String! 是指 java 虚拟机的平台类型;平台类型客观存在,不能主观定义;
    2. 需要根据平台代码自行确定是否为可空类型;
kotlin类型转换
  1. 基础用法
if (p is Person) {
    println(p.name);
}
  1. 作用范围
    如果变量为全局变量,if判断不为空语句块中,变量类型不会转为非空类型;
var value: String? = null
value = "hi"
if (value != null) {
    // if 判断不为空的语句块中,value是 String 类型;
    println(value.length)
}
// if 判断不为空的语句块外,value还是 String? 类型

  1. 类型安全转换
val kotliner: Kotliner = ...
println((kotliner as? Person)?.name)
  1. 使用建议
    • 尽量使用 val 来声明不可变引用,让程序的含义更加清晰确定;
    • 尽可能减少函数对外部变量的访问,也为函数式编程提供基础;
    • 必要时创建局部变量指向外部变量,避免因它变化引起程序错误;
  2. kotlin使用类做参数时写法
Java
Person.class
Kotlin
Person::class.java
常量和变量
  1. var 变量
  2. val 严格讲为只读变量、通常作为常量使用,为运行时常量;(读取值不一定相同)
  3. const val 为编译时常量;
// 读取 b 属性时,值不一定相同;
class X {
    val b: Int
        get() {
            return (Math.random() * 100).toInt()
        }
}
  1. 常量值
    使用 const 关键字的要求:
    1. 只能定义在全局范围;
    2. 只能修饰基本类型;
    3. 必须立即用字面量初始化;
// 对标 Java static final int
const val b = 3
  1. 常量引用
    自定义类对象的引用即为常量引用;
运算符与中缀表达式
  1. 自定义运算符演示
operator fun String.times(right: Int): String {
    return (1..right).joinToString("") { this }
}

fun main() {
    var star = "*"
    println(star * 5)
}
// 输出结果为 *****
  1. 中缀表达式 函数前使用 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 表达式
  1. lambda 表达式是匿名函数的语法糖
  2. 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)
}
高阶函数

高阶函数是指参数类型包含函数类型或返回值类型为函数类型。

  1. 函数类型作为最后一个参数可移动到括号外变;
  2. 只有一个lambda表达式时,可以省略小括号;
  3. 只要一个参数的lambda表达式,表达式中形参默认为 it ;
  4. 基础样例
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())
        }
    }
}
内联函数
  1. 在函数前加 inline 表示内联函数;
  2. 内联函数更适用于高阶函数;
  3. 函数本身被内联到调用处;
  4. 函数的函数参数被内联到调用处;
  5. 内联函数减少了函数的调用,对系统开销优化;
  6. 内联高阶函数的 return
    1. 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
     */
    
    1. non-local return 从外部函数返回
    fun main() {
        nonLocalReturn {
            return // 从main函数返回
        }
    }
    
    1. 不是所有内联函数都可以 non-local return ;函数在内联函数调用处与定义处不在同一个上下文,有可能存在不合法的 non-local return;
      1. 禁止 non-local return ;在内联函数的形参函数前加 crossinline 关键字;
      2. 还可以使用 noinline 关键字,禁止内联;对性能没有改善,不推荐使用;
  7. 内联属性 ?
  8. 内联函数的限制
    1. public/protected 的内联方法只能访问对应类的 public 成员;
    2. 内联函数的内联函数参数(没有标记 noinline)不能被存储(赋值给变量);
    3. 内联函数的内联函数参数(没有标记 noinline)只能传递给其他内联函数参数;
有用的高级函数
  1. let // 推荐,返回表达式的结果
var r = X.let { x -> R}
  1. run // 返回表达式的结果
var r = X.run {this: X -> R}
  1. also // 推荐,返回 Receiver
var x = X.also { x -> Unit}
  1. apply // 返回 Receiver
var x = X.also {this: X -> Unit}
  1. use // 推荐,自动关闭资源
var r = Closeable.use { c -> R}
集合变换与序列
  1. 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")
}
  1. flatMap 变换
    集合中的所有元素一一映射到新的集合并合并这些集合得到新的集合;
???
  1. 集合的聚合操作
    1. sum 所有元素求和
    2. reduce 将元素一次按规则聚合,结果与元素类型一致;
    3. 给定初始值,将元素按规则聚合,结果与初始值类型一致;
SAM 转换
JavaKotlin
Java 接口支持支持
Kotlin 接口支持不支持
Java 方法支持支持
Kotlin 函数(方法)支持不支持
抽象类不支持不支持
代理 Delegate
  1. 接口代理
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() 
    }
}
  1. 属性代理
    1. lazy : 代理了 val属性的 getter 方法;
    2. observable : 代理了 gettersetter 方法;
      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;
    }
    
    1. vetoable 的使用场景及原理 ???
    2. 属性代理在简化配置读写方面的作用 ???
    3. 分析 notNull 的使用方法并与 lateinit 做对比 ???
    4. 使用属性代理实现 SharedPreferences 的读写 ???
单例 object
  1. 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()
  1. 普通类中 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() {}
    }
}

  1. object 的构造器有系统生成默认无参的方法了,不能自己定义,可以使用 init 块初始化;
  2. object 的类继承和普通了一致;
内部类
  1. 内部类的定义
// 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()

  1. 内部 object
    内部 objcet 不存在非静态的情况,不可用 inner 修饰;
object OuterObject {
    object StaticInnerObject
}
  1. 匿名内部类
    1. 匿名内部类基础实现
    object: Runable {
        override fun run() {...}
    }
    
    1. 匿名内部类实现多个接口
    object: Cloneable, Runnable {
        override fun run() {...}
    }
    
data class 数据类

class 前加 data 关键字;

  1. 数据量定于与使用
// 定义在主构造器中的属性又称为 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

  1. JavaBean vs data class
JavaBeandata class
构造方法默认无参构造属性作为参数
字段字段私有,Getter/Setter公开属性
继承性可继承也可被继承不可被继承
component相等性、解构等
  1. 合理使用 data class
    1. 属性类型最好为基本类型、String、其他 Data Class ;
    2. 最好不要自定义属性的 Getter-Setter
    3. 属性最好是不可变的(val);
枚举类
  1. 定义
// java
enum State {
    Idle, Busy
}

// kotlin
enum class State {
    Idle, Busy
}

// 使用
State.Idle.name // Idle
State.Idle.ordinal // 0
  1. 定义构造器
enum class State(val id: Int) {
    Idle(0), Busy(1)
}
  1. 枚举类实现接口
    枚举的父类是 Enum ,所以不能继承其他类;
    1. 统一实现
    enum class State: Runnable {
        Idle, Busy
        override fun run() {
            println("For every state.")
        }
    }
    
    1. 各自实现
    enum class State: Runnable {
        Idle {
            override fun run() {...}
        },Busy {...};
    }
    
  2. 枚举类定义扩展
fun State.next(): State {
    State.values().let {
        val nextOrdinal = (oridinal + 1) % it.size
        it[nextOrdinal]
    }
    
}

  1. 条件分支
val value = when(state) {
    State.Idle -> {0}
    State.Busy -> {1}
}
  1. 枚举比较
  2. 区间
enum class Color {
    White, Red, Green, Blue, Yellow
}

val colorRange = Color.White..Color.Green
val color = Color.Blue
color in colorRange // false
密封类

关键字 sealed ,在 class 之前;

  1. 概念
    1. 密封类是一种特殊的抽象类;
    2. 密封类的子类定义在与自身相同的文件中;
    3. 密封类的子类的个数是有限的;
内联类

关键字 inline ,在 class 之前;
1.概念
1. 内联类是对某一个类型的包装
2. 内联类是类似于 Java 装箱类的一种类型
3. 编译器会尽可能使用被包装的类型进行优化;
4. 内联类在 1.3 中处于公测阶段,谨慎使用;

数据类的 json 序列化
  1. Gson
    反序列化不能识别默认参数;
  2. Moshi
  3. Kotlinx.serialization
  4. 框架对比
GsonMoshiK.S
空类型反射、注解
默认值反射、注解
initNoArg插件反射、注解
Java
跨平台
泛型
  1. 泛型的基本声明方法
    函数声明泛型

    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")
    
  2. 多个约束

    fun <T> callMax(a:T, b:T)
        where T: Comparable<T>, T: () -> Unit {
        if(a > b) a() else b()    
    }
    
  3. 泛型的型变

    1. 不变
    2. 协变 关键字 out // 生产者,一般为方法的返回值;
    3. 逆变 关键字 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)
      
      }
      
    4. 个人总结
      协变和逆变使泛型有继承关系的类具有继承关系;
  4. 星投影

    1. ‘*’ 可用在变量类型声明的位置;
    2. ‘*’ 可以描述一个未知的类型;
    3. ‘*’ 所替换的类型在:
      • 协变点返回泛型参数上限类型;
      • 逆变点接受泛型参数下限类型;
    4. ‘*’ 不能直接或间接应用在属性或函数上,以下为错误示范:
      • QueryMap<String, *>()
      • maxOf<*>(1, 3)
    5. ‘*’ 适用于作为类型的描述的场景,以下为正确示范:
      • val queryMap: QueryMap<*, *>
      • if(f is Function<*, *>){...}
      • HashMap<String, List<*>>()
  5. 内联特化

inline fun <reified T> genericMethod(t: T) {
    //val t = T() // 报错,不允许
    val ts = Array<T>(3) {...}
    val jclass = T::class.java
    val list = ArrayList<T>()
}
反射
  1. 使用kotlin反射需要引入反射依赖
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
    implementation "org.jetbrains.kotlin:kotlin-reflect"
    
  2. 基本数据结构
    • KType :描述未参数的类型或反向参数等,例如 Map<String, Int> ;可通过 typeOf 或者以下类型获取对应的父类、属性、函数参数等;
    • KClass :描述对象的实际类型,不包含泛型参数,例如 Map ;可通过对象、类型名直接获取
    • KProperty :描述属性,可通过属性引用,属性所在类的 KClass 获取;
    • KFunction :描述函数,可通过函数易用、函数所在类的 KClass 获取;
  3. 反射数据结构: Kotlin vs Java
KotlinJava
KTypeType
KClassclass
KPropertyField
KFunctionMethod
  1. Kotlin 中使用反射
    1. Java 反射
      • 优点:无需引入额外依赖,首次使用速度相对较快
      • 缺点:无法访问 Kotlin 语法特性,需对 Kotlin 生成的字节码足够了解;
    2. Kotlin 反射
    • 优点:支持访问 Kotlin 几乎所有特性,API设计更友好;
    • 缺点:引入 Kotlin 反射库(2.5MB, 编译后400KB),首次调用慢;
  2. Kotlin | Java 浅复制和深复制
  3. 深复制案例 | Model 映射阿案例
协程
  1. 作用

    • 协程可以让异步代码同步化;
    • 协程可以降低异步程序的设计复杂度;
    • 挂起和恢复可以控制执行流程的转移;
    • 同步代码比异步代码更灵活,更容易实现复杂业物;
  2. 线程 vs 协程
    线程Thread: 指操作系统的线程,也称为内核线程;
    协程Coroutine: 指语言实现的协程,运行在内核线程之上;

  3. 协程的分类

    1. 按调用栈
      • 有栈协程
      • 无栈协程
    2. 按调用关系
      • 对称协程:调度权可以转移给任意协程,协程之间是对等关系;
      • 非对称协程:调度只能转移给调用自己的协程,协程存在父子关系;
  4. kotlin 协程的基本要素

    1. 挂起函数。关键字 suspend 写在 fun 之前;
    2. 调用挂起函数的位置,表示挂起点;
    3. 挂起函数只能在其他挂起函数协程中调用;
    4. 挂起函数调用是包含了协程"挂起"的语义;
    5. 挂起函数返回时则包含了协程"恢复"的语义;
    6. kotlin 是无栈协程
    7. 挂起函数的类型
      // 函数类型: 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 {...}
      
      
    8. suspend 函数中,只有切线程才会真正挂起, Continuation
  5. 将回调转写成挂起函数

    • 使用 suspendCoroutine 获取挂起函数的 Continuation ;
    • 回调成功的分支使用 Continuation.resume(value) ;
    • 回调失败则使用 Continuation.resumeWithException(e) ;
  6. 创建协程

    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 函数会被传给该实例作为协程的实际执行体;
  7. 协程上下文

    1. 协程执行过程中需要携带数据

    2. 索引是 CoroutineContext.Key ;

    3. 元素是 Coroutine Context.Element ;

    4. 拦截器

      • 拦截器 ContinuationInterceptor 是一类协程上下文元素;
      • 可以对协程上下文所在协程的 Continuation 进行拦截;
      interface ContinuationInterceptor: CoroutineContext.Element {
          fun <T> interceptContinuation(
              continuation: Continuation<T>): Continuation<T>
          ...
      }
      
    5. Continuation 执行示意
      当挂起函数 suspend{...} 中包含挂起函数 a()时,a() 使用 suspendCoroutineapi 获取 suspend{...}continuatian 并挂起,然后执行挂起函数 a(); 在 a() 中或其他位置调用 continuatian.resume(...) 重新恢复 suspend{...} 的执行;

      suspend {
          a()
      }.startCoroutine(...)
      
      suspend fun a() = suspendCoroutine<Unit> {
          thread {
              it.resume(Unit)
          }
      }
      
      • 其中 it 表示 SafeContinuation ;
      • SafeContinuation 的作用就是确保:
        1. resume 只被调动一次
        2. 如果在当前线程调用栈上直接调用则不会挂起;
    6. 拦截 Continuation

      suspend {
          a()
      }.startCoroutine(...)
      
      1. SafeContinuation 仅在挂起点时出现;
      2. 拦截器在每次(恢复)执行协程体时调用;
      3. SuspendLambda 是协程函数体;
Kotlin 协程框架
  1. Kotlin 协程的启动模式
启动模式功能特性
DEFAULT立即开始调度协程体,调度前若取消则直接取消
ATOMIC立即开始调度,且在第一个挂起点前不能被取消
LAZY只要在需要(start/join/await)时开始调度
UNDISPATCHED立即在当前线程执行协程体,直到遇到第一个挂起点(后面取决于调度器)
  1. Kotlin 协程的调度器
调度器Java VmNative
Default线程池主线程循环
MainUI线程同 Default
Unconfined直接执行直接执行
IO线程池
  1. 其他特性

    1. Channel: "热"数据流,并发安全的通信机制;
    2. Flow: "冷"数据流,协程的响应式 API;
    3. Select: 可对多个挂起事件进行等待;
  2. 作用域对异常传播的影响

类型生产方式异常传播特征
顶级通过 GlobalScope 创建不向外部传播
协同Job 嵌套、coroutineScope 创建双向传播
主从通过supervisorScope创建,与内部直接子协程主从,与外部协同自上而下 单向传播
  1. 异常处理器对 async 是没有意义的; await调用是抛出该异常,一会取消外部作用域;

  2. android 项目协程框架依赖配置
    最新版本在:https://github.com/Kotlin/kotlinx.coroutines

    implementation "org.jetbrains.kotlin:kotlinx-coroutines-android:1.3.8"
    
Channel
  1. Channel 的关闭

    1. 调用 close 关闭 Channel ;
    2. 关闭后调用 send 抛异常,isClosedForSend 返回 true ;
    3. 关闭后调用 receive 可接受缓存的数据;
    4. 缓存消费完后 receive 抛异常,isClosedForReceive 返回 true ;
  2. Channel 的迭代

    • hasNext 在有缓存的数据时范湖 true ;
    • hasNext 在未关闭且缓存为空时挂起;
    • hasNext 在正常关闭且缓存为空时返回 false ;
    for (i in channel) {
        log("received:", i)
    }
    
  3. Channel 的协程 Builder

    • produce :启动一个生产者协程,返回ReceiveChannel
    • 通过 Builder 启动的协程结束后自动关闭对应的 Channel ;
  4. BroadcastChannel

    • Channel 的元素只能被一个消费者消费;
    • BroadcastChannel 的元素会分发给所有的订阅者;
    • BroadcastChannel 不支持 RENDEZVOUS ;
Flow
  1. 使用 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)
    }
    
  2. 异常处理
    flow {
        emit(1)
        throw ArithmeticException("Div 0")
    }.catch{t: Throwable ->
        log("caught error: $t")
    }.onCompletion{t: Throwable? ->
        // 这里的t是指上吗的catch未捕获的异常
        log("finally")
    }
    
  3. Flow 的取消
    1. Flow 的运行依赖于协程;
    2. Flow 的取消取决于 collect 所在协程的取消;
    3. collect 作为挂起函数可以响应所在协程的取消状态;
  4. 从集合创建 Flow
    listOf(1,2,3,4).asFlow()
    setOf(1,2,3,4).asFlow()
    flowOf(1,2,3,4)
    
  5. Channel 创建 Flow
    val channel = Channel<int>()
    channel.consumeAsFlow()
    
  6. Flow 元素并发生成
    Flow 不是线程安全的,应使用 channelFlow ;
    channelFlow {
        send(1)
        withContext(Dispatchers.ID) {
            send(2)
        }
    }
    
  7. Back Pressure
    指消费者速度慢与生成者速度;
    • buffer: 指定固定容量的缓存;
    • conflate: 保留最新的指;
    • colletLatest: 新值发送时取消之前的;
    flow {
        emit(1)
        delay(50)
        emit(2)
    }.collectLatest{value ->
        println("Collectin $value")
        delay(100)
        println("$vale collected")
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值