学废kotlin

468 阅读5分钟

协程

suspendCancellableCoroutine 阻塞当前协程,并是可取消的协程


//挂起当前协程,如果不执行resume或者cancel会一直挂起  
val r = suspendCancellableCoroutine<String> {  
  
    cancellableContinuation: CancellableContinuation<String> ->  
  
    cancellableContinuation.invokeOnCancellation {  
        // 取消后回调,正常结束不会回调  
    }  
  
    cancellableContinuation.resume("3")//恢复协程,返回结果 r="3"  
  
    cancellableContinuation.cancel(Throwable(""))//取消协程的运行  
  
    cancellableContinuation.resumeWithException(Throwable(""))//带着错误恢复协程  
  
}

coroutineScope 创建新的协程作用域

只能在协程中运行

CoroutineScope 创建新的协程作用域 哪里都可以运行

    var coroutineScope = CoroutineScope(Dispatchers.Default)  
    coroutineScope.launch {  
  
    }

Semaphore 限制协程的数量 (原理:信号量控制)

    val maxConcurrency = 3 
    val semaphore = Semaphore(maxConcurrency) 
    val jobs = (1..10).map { 
        launch { 
            semaphore.withPermit { 
                println("开始执行协程 $it") 
                delay(1000) 
                println("协程 $it 完成") 
                } 
            } 
        } 
    jobs.joinAll()

在这个例子中,我们创建了一个允许最多3个协程同时运行的Semaphore。通过withPermit方法,我们确保在执行协程时遵循这个限制。

协程源码类

SchedulerCoroutineDispatcher 协程任务分发

CoroutineScheduler 创建线程的子类Worker 分发任务 类似于线程池

协程执行过程


suspend fun gg() {  
  
suspendCancellableCoroutine<String> {  
  
println("")  
  
}  
}

上面代码中,会由于执行了suspendCancellableCoroutine而没有调用resume函数导致协程被挂起

是因为没有调用resume,suspendCancellableCoroutine会使gg返回 COROUTINE_SUSPENDED,源码如下:

企业微信截图_16969334715975.png

如果返回 COROUTINE_SUSPENDED,会导致没有调用到后续代码,我们可以在下面的反编译代码中看到,只会执行case 0:,就return了,后面的lable执行不到了,就相当于挂起,当我们再次调用resumeWith时,这时的lable为1,才会执行case 1及后面的代码。

理解这些后,然后看一下协程执行中,是如何执行到这一步的

下面是调用栈

image.png

也就是说,由于suspend的标志,会由协程的环境调用这个函数,而 invokeSuspend是kotlin自动实现的函数,反编译java后可以看到这个实现,如图:

image.png

可以看到launch里面的执行内容都放到,该invokeSuspend里面执行了

launch返回的job执行->执行launch的块级函数->执行invokeSuspend

同样,如果直接返回COROUTINE_SUSPENDED,也会导致协程挂起,如图,只打印了一个sdf

image.png

dispatcher任务分发: 协程的执行会把要执行的内容封装成Task对象,然后放到Worker的localQueen的task队列中,如果task都执行完成后,会执行Unsafe.park,阻塞cpu,如果有新task进来,会调用一次Unsafe.unpark再将线程唤醒。

总结

所以协程的大致执行过程创建continuation(或者叫协程,因为Corountine实现了continuation)用来执行传进来的suspend函数 - > 通过interceptContinuation创建DispatchedContinuation,作为dispatcherh和continuation桥梁,DispatchedContinuation本身会引用continuation,同时也实现了Task类 -> 执行DispatchedContinuation的函数resumeWithresumeWith会通过dispatcher将task(DispatchedContinuation)分发到线程池(或者叫work池)中的一个work(线程)的待执行队列中 -> 等待执行Task(也就是DispatchedContinuation),执行task时会调用continuation.resumeWith,用来执行传进来的suspend函数

注:Work是一个线程,继承了Thread

Flow

collectLatest 接收最新值,抛弃旧值

终端流操作符通过提供的action收集给定的流。与collect的关键区别在于,当原始流发出新值时,先前值的action块将被取消。
可以通过下面的例子来证明:
flow {
    emit(1)
    delay(50)
    emit(2)
}.collectLatest { value ->
    println("Collecting $value")
    delay(100) // Emulate work
    println("$value collected")
}

打印“Collecting 1Collecting 2Collecting 2

原理就一行代码

public suspend fun <T> Flow<T>.collectLatest(action: suspend (value: T) -> Unit) {  
    mapLatest(action).buffer(0).collect()  
}

mapLatest

collect

终端流运算符收集给定的流但忽略所有发出的值。如果在收集期间或在提供的流程中发生任何异常,则从此方法重新抛出此异常。
它是collect {}的简写。
该运算符通常与onEach 、 onCompletion和catch运算符一起使用,以处理所有发出的值并处理上游流中或处理过程中可能发生的异常,例如:
flow
    .onEach { value -> process(value) }
    .catch { e -> handleException(e) }
    .collect() // trigger collection of the flow

  1. flow:这是创建Flow的最基本的函数。你可以在flow的lambda表达式中发射多个值。
val flow = flow {
    emit(1)
    emit(2)
}
  1. collect:这是用于收集Flow中的值的函数。它是一个挂起函数,所以它应该在协程中调用。
launch {
    flow.collect { value ->
        println(value)
    }
}
  1. map:这个函数用于将Flow中的每个值转换成另一种形式。
val stringFlow = flow.map { value ->
    value.toString()
}
  1. filter:这个函数用于过滤Flow中的值。
val evenFlow = flow.filter { value ->
    value % 2 == 0
}
  1. reduce:这个函数用于将Flow中的所有值组合成一个单一的结果。
val sum = flow.reduce { accumulator, value ->
    accumulator + value
}
  1. onEach:这个函数在每次从Flow中发射出一个值时调用。
flow.onEach { value ->
    println("Value emitted: $value")
}
  1. catch:这个函数用于处理Flow中的异常。
flow.catch { e ->
    println("Caught exception: $e")
}
  1. onStart:这个函数在开始收集Flow之前调用。
flow.onStart {
    println("Flow started")
}
  1. onCompletion:这个函数在Flow收集完成后调用,无论成功还是失败。
flow.onCompletion { cause ->
    if (cause != null) {
        println("Flow completed with exception: $cause")
    } else {
        println("Flow completed successfully")
    }
}

以上就是Kotlin Flow API中的一些主要函数,实际上还有更多的函数和操作符,如:buffer、conflate、zip等。

  1. buffer:用于在Flow中添加缓冲区,以便在发射和收集操作之间进行并发处理。
flow.buffer().collect { value ->
    println(value)
}

带buffer的flow(buffer容量>0)是会异步执行,而不带buffer(或者buffer容量=0)的类型是同步执行,也就是说带有buffer的flow是可以收集和发送一起执行的,否则只能发送一个收集一个,依次执行。

  1. conflate:用于在发射速度快于收集速度时,丢弃中间值,只保留最新的值。
flow.conflate().collect { value ->
    println(value)
}
  1. zip:用于将两个Flow的值按顺序组合成一个新的Flow。
val flow1 = flowOf(1, 2, 3)
val flow2 = flowOf("a", "b", "c")
val zippedFlow = flow1.zip(flow2) { value1, value2 ->
    "$value1$value2"
}
  1. combine:用于将两个Flow的值组合成一个新的Flow,当任一Flow发射新值时都会触发组合。
val flow1 = flowOf(1, 2, 3)
val flow2 = flowOf("a", "b", "c")
val combinedFlow = flow1.combine(flow2) { value1, value2 ->
    "$value1$value2"
}
  1. flatMapConcat:用于将Flow中的每个值转换为另一个Flow,并按顺序连接它们。
val nestedFlow = flowOf(flowOf(1, 2), flowOf(3, 4))
val flatFlow = nestedFlow.flatMapConcat { innerFlow ->
    innerFlow
}
  1. flatMapMerge:用于将Flow中的每个值转换为另一个Flow,并同时处理它们。
val nestedFlow = flowOf(flowOf(1, 2), flowOf(3, 4))
val flatFlow = nestedFlow.flatMapMerge { innerFlow ->
    innerFlow
}
  1. debounce:用于在Flow中添加去抖动操作,以便在指定的时间间隔内仅保留最后一个值。
flow.debounce(300).collect { value ->
    println(value)
}
  1. distinctUntilChanged:用于过滤掉连续重复的值。
val flow = flowOf(1, 1, 2, 2, 3)
val distinctFlow = flow.distinctUntilChanged()

flow源码

调用flow函数是调用的safeFlow


public fun <T> flow(@BuilderInference block: suspend FlowCollector<T>.() -> Unit): Flow<T> = SafeFlow(block)

flow作用域是属于FlowCollector的

flow{
    //  FlowCollector作用域
}

SafeCollector实现了FlowCollector

emitFlowCollector函数

emit执行过程

override suspend fun emit(value: T) {  
           return suspendCoroutineUninterceptedOrReturn sc@{ uCont ->  
                    try {  
                            emit(uCont, value)  
                        } catch (e: Throwable) {  
                            lastEmissionContext = DownstreamExceptionContext(e, uCont.context)  
                            throw e  
                            }  
                        }  
                    }

1.suspend和suspendCoroutineUninterceptedOrReturn标识异步执行,并在resumeWith之前阻塞当前协程

private fun emit(uCont: Continuation<Unit>, value: T): Any? {  
......
//这里的emitFun会调用collector.emit(value) 这是kotlin函数对象的原始写法
//因为emitFun的定义写在了类外面,所以第一次参数是调用这个函数的对象,如果emitFun的定义写在了类里面,调用这个函数的对象就不需要作为该函数的参数。
val result = emitFun(collector as FlowCollector<Any?>, value, this as Continuation<Unit>)  

.......

return result  
}

所以现在相当于调用了collector.emit函数,collector是调用collect时,collect的唯一参数。

我们调用无参数的collect(),实际上是调用的扩展函数,里面有个空实现的NopCollector(Nop其实是一个汇编指令,表示什么也不做,但是cpu会去执行这个指令,但没有什么作用,也占用一个指令执行的时间)

image.png

image.png

所以这里是调用了空实现的emit

当我们调用collect()表示我要收集数据了,这里调用到了collectSafely,这是safeflow实现的一个方法,

image.png

image.png

所以这里相当于执行了flow的块级函数,高亮的那一部分就是

image.png

然后执行emit -> 执行NopCollect.emit方法

如果像执行map{}这样的函数呢?

image.png

然后看transfrom

image.png

unsafeFlow其实是一个简单的flow实现

image.png

他和SafeFlow的区别就是,safeFlow在执行collect时,是由SafeCollector来执行,因为SafeCollector本身是一个continuation,他在emit执行时会检查当前所在的协程环境是否合法,是否处于active状态,因为协程是可以被取消的。

比如: image.png

我们可以看到一个基本的协程其实是实现了Job image.png

现在回到unsafeFlow,其实随着一层层调用,最后还是回到了SafeFlow,这你就要问了,为什么要多创建一个unsafeFlow出来,因为我们想要执行emit函数 将处理过的值再发送到下游,下游再次调用colect时,我们不想让他再次执行到collect中的块级函数,所以多封装一层,只执行上一层的collect,而不执行上上一层的collect。

第一层的lambda函数会执行到emit,由emit调用第二层的lambda函数,逐级调用到最后。

flowOn可以设置一个上游协程环境,其实是将其collect函数,运行在设置的协程中,那么执行上几层的collect时都会运行在该协程环境中,又由于emit会继承协程环境,所以后面的collect块级函数同样也会运行在该协程环境中,其实不继承该协程环境也会运行在该协程中,只是取出协程对象,判断该协程对象是否处于active状态来判断是否发送出该值。

这些仅是Kotlin Flow API中的一部分函数和操作符。要了解更多,请参阅官方文档:Kotlin Flow API

flow的buffer扩展函数,其中buffer的实现原理使用的是Channel,emit发送值,最终会调用到channel的send方法,接收到值后再通过emit方法发送到下游。

最后一张图总结:

MVIMG_20231205_150350(1).jpg

Channel

场景:

协程:a 协程:b

在协程a发送消息,在协程b接收

相对于flow更灵活一点,flow对于发送消息灵活,但是接收消息不灵活。发送消息我们可以通过flowCollector的emit方法在任何地方发送(需要保存flowCollector对象),但是接收消息却在链式调用中已经写死了。而对于channel只需要send,receive即可。

Select

这样一个场景,两个 API 分别从网络和本地缓存获取数据,期望哪个先返回就先用哪个做展示,这是侯就需要用到select 对于支持select的地方有这些:

* | **Receiver**     | **Suspending function**                           | **Select clause**
* | ---------------- | ---------------------------------------------     | -----------------------------------------------------
* | [Job]            | [join][Job.join]                                  | [onJoin][Job.onJoin]
* | [Deferred]       | [await][Deferred.await]                           | [onAwait][Deferred.onAwait]
* | [SendChannel]    | [send][SendChannel.send]                          | [onSend][SendChannel.onSend]
* | [ReceiveChannel] | [receive][ReceiveChannel.receive]                 | [onReceive][ReceiveChannel.onReceive]
* | [ReceiveChannel] | [receiveCatching][ReceiveChannel.receiveCatching] | [onReceiveCatching][ReceiveChannel.onReceiveCatching]
* | none             | [delay]                                           | [onTimeout][SelectBuilder.onTimeout]

语法糖

class PP { 

    operator fun get(kk: PP) = 0  
  
    companion object Key {  
  
    }  
  
}

get 相当于 []

例如:

val p = PP()
p[PP] 相当于 p.get(PP)

PP既可以当作伴随对象也可以当作PP类

伴随对象只能有一个

例如上面的p[PP]相当于p[PP.Key]

  1. 属性代理

在Kotlin中,属性代理是一种设计模式,允许你将属性的getter和setter操作委托给另一个对象。这个对象被称为代理对象,它需要实现getValuesetValue方法。

provideDelegate方法是Kotlin 1.1引入的一个新特性,它允许你在创建属性代理时进行一些额外的检查或初始化工作。

下面是一个使用provideDelegategetValue方法的例子:

import kotlin.reflect.KProperty

class Delegate {
    operator fun provideDelegate(thisRef: Any?, prop: KProperty<*>): Delegate {
        println("provideDelegate for ${prop.name}")
        return this
    }

    operator fun getValue(thisRef: Any?, prop: KProperty<*>): String {
        return "$thisRef, thank you for delegating '${prop.name}' to me!"
    }
}

class Example {
    var p: String by Delegate()
}

fun main() {
    val e = Example()
    println(e.p)
}

在这个例子中,Delegate类实现了provideDelegategetValue方法。当我们在Example类中创建一个由Delegate类代理的属性p时,provideDelegate方法会被调用,我们可以在这个方法中进行一些初始化工作。然后,当我们访问属性p时,getValue方法会被调用,我们可以在这个方法中定义属性的getter操作。

需要注意的是,provideDelegate方法是可选的,如果你的代理对象不需要在创建时进行一些额外的操作,你可以不实现这个方法。但是getValue方法是必须的,因为它定义了属性的getter操作。