kotlin协程使用笔记

kotlin协程使用笔记,第1张

简介:

协程相对独立,有自己的上下文,由自己控制切换。协程的切换在用户态完成,不涉及内核 *** 作,相比线程的用户态内核态切换开销更小。协程是一个可以挂起的特殊函数,当前协程被暂停后可以选择切换到其它协程执行,并等待其它协程任务结束后再切回来继续执行(代替回调)。一个线程可以有多个协程,通过分时复用的方式进行切换。一个线程内的多个协程是串行执行的。
协程可以通过suspend关键字来标志耗时 *** 作。
创建协程

RunBlocking:
使用:

runBlocking {
    log("runBlocking启动一个协程")
}

源码:

@Throws(InterruptedException::class)
public fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T {
    //省略代码……

    //JoinBlocking会阻塞线程
    return coroutine.joinBlocking()
}

启动一个新的协程并阻塞调用它的线程,直到里面的代码执行完毕,返回值是泛型T

context:CoroutineContext: 协程上下文block suspend CoroutineScope.() -> T: 协程体CoroutineScope: 协程作用域 (为协程的执行提供一个环境,区别于协程上下文)返回参数: T

GlobalScope.launch
使用:

GlobalScope.launch {
    log("GlobalScope.launch启动一个协程")
}

源码:
GlobalScope:

public object GlobalScope : CoroutineScope {
    override val coroutineContext: CoroutineContext
        get() = EmptyCoroutineContext
}

GlobalScope.launch:

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

launch没有调用joinBlocking,不会阻塞当前线程。它是开启另一个线程来执行协程体。
必须要在协程作用域(CoroutineScope)中才能调用,默认返回值StandaloneCoroutine是一个Job。

Job:代表一个异步的任务,具有生命周期,可以判断任务是否执行和取消任务。CoroutineContext :协程上下文CoroutineStart:启动模式block:协程执行块,返回值是空

GlobalScope.async
使用:

GlobalScope.async {
    log("GlobalScope.async启动一个协程")
}

源码:

public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T> {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyDeferredCoroutine(newContext, block) else
        DeferredCoroutine<T>(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

默认返回值DeferredCoroutine是一个 Deferred。
你可以使用 deferred的await() 在一个延期的值上得到它的最终结果, 但是 Deferred 也是一个 Job,所以如果需要的话,你可以取消它。

阻塞测试:
log("click start")
runBlocking {
    delay(2000)
    log("runBlocking")
}
GlobalScope.launch {
    delay(2000)
    log("launch")
}
GlobalScope.async {
    delay(2000)
    log("async")
}

打印:

<20:58:39>  <main>  click start
<20:58:41>  <main>  runBlocking
<20:58:43>  <DefaultDispatcher-worker-1>  launch
<20:58:43>  <DefaultDispatcher-worker-3>  async

分析:
runBolcking会阻塞当前线程直到协程体执行完成,运行在当前main线程;
launch、async不会阻塞线程,运行的线程不给指定时则随机一个线程。
launch、async是在等runBlocking执行完(2秒后)后一起执行,再执行2秒后同时结束。

返回值测试:
val runBlockingJob = runBlocking {
    log("runBlocking 启动一个协程")
}
log("runBlockingJob= $runBlockingJob")//kotlin.Unit

val launchJob = GlobalScope.launch {
    log("launch 启动一个协程")
}
log("launchJob= $launchJob")//StandaloneCoroutine

val asyncJob = GlobalScope.async {
    log("async 启动一个协程")
}
log("asyncJob= $asyncJob")//DeferredCoroutine

打印:

<20:02:31>  <main>  runBlocking 启动一个协程
<20:02:31>  <main>  runBlockingJob= kotlin.Unit
<20:02:31>  <main>  launchJob= StandaloneCoroutine{Active}@babfe80
<20:02:31>  <DefaultDispatcher-worker-1>  launch 启动一个协程
<20:02:31>  <main>  asyncJob= DeferredCoroutine{Active}@6c68cb9
<20:02:31>  <DefaultDispatcher-worker-1>  async 启动一个协程

小结:

runBlocking{} - 主要用于测试
该方法的设计目的是让suspend风格编写的库能够在常规阻塞代码中使用,常在main方法和测试中使用。

GlobalScope.launch/async{} - 不推荐使用
由于这样启动的协程存在启动协程的组件已被销毁但协程还存在的情况,极限情况下可能导致资源耗尽,因此并不推荐这样启动,尤其是在客户端这种需要频繁创建销毁组件的场景。

自定义Coroutine的上下文
fun start3(){
    //方式1
    val scope = CoroutineScope(EmptyCoroutineContext)//CoroutineScope函数
    scope.launch {
        log("scope launch")
    }
    scope.async {
        log("scope async")
    }
    //方式2
    class MyCoroutineScope : CoroutineScope{
        override val coroutineContext: CoroutineContext
            get() = EmptyCoroutineContext //同方式1,都使用EmptyCoroutineContext 
    }
    val myCustomScope = MyCoroutineScope()
    myCustomScope.launch {
        log("myCustomScope launch")
    }
    myCustomScope.async {
        log("myCustomScope async")
    }
}
概念详解 CoroutineContext

协程作用域包含协程上下文。生命周期、拦截器、线程控制皆上下文。通过上下文控制线程和生命周期。

CoroutineContext的四种元素(Element)

@SinceKotlin("1.3")
public interface CoroutineContext {
//省略……
}
//协程上下文的一个元素本身就是一个上下文
public interface Element : CoroutineContext {}

//控制协程的生命周期的Element
public interface Job : CoroutineContext.Element {}
//指定协程名称的Element
public data class CoroutineName(val name: String) : AbstractCoroutineContextElement(CoroutineName) {}
//向合适的线程分发任务的Element
public abstract class CoroutineDispatcher :AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {}
//处理未被捕捉的异常的Element
public interface CoroutineExceptionHandler : CoroutineContext.Element {}

这四种元素的key都是单例的,当添加多个CoroutineName(或者ContinuationInterceptor/…)时,只有一个会生效。

public companion object Key : CoroutineContext.Key<XXX>

CoroutineContext: 协程上下文
类似一个集合,用来盛放 Element 元素,保存用户定义的数据。每个 Element 在这个集合有一个唯一的 Key

Job: 控制协程的生命周期

CoroutineDispatcher: 向合适的线程分发任务(类似rxjava的schedule)

CoroutineName: 协程的名称,调试的时候很有用

CoroutineExceptionHandler: 处理未被捕捉的异常

CoroutineContext 的接口
public interface CoroutineContext {
public operator fun <E : Element> get(key: Key<E>): E?

public fun <R> fold(initial: R, operation: (R, Element) -> R): R

    public operator fun plus(context: CoroutineContext): CoroutineContext{...}

    public fun minusKey(key: Key<*>): CoroutineContext

    public interface Key<E : Element>

    public interface Element : CoroutineContext {...}
}

get()方法:
通过key获取Element。
CoroutineName(“myContext”).get(CoroutineName.Key)等价CoroutineName(“myContext”)[CoroutineName.Key],类似map[key]。

fold():
遍历所有Element
类似Collections的 fold扩展函数:

public inline fun <T, R> Iterable<T>.fold(initial: R, operation: (acc: R, T) -> R): R {
    var accumulator = initial
    for (element in this) accumulator = operation(accumulator, element)
    return accumulator
}

plus():
CoroutineContext 可以使用 " + " 运算符进行合并。
CoroutineContext+CoroutineContext=>返回新的CoroutineContext。新Context包含俩context里的所有Element,如果有重复的key元素,则后+的元素覆盖之前的元素。
类似kotlin Set的plus扩展函数:

public operator fun <T> Set<T>.plus(element: T): Set<T> {
    val result = LinkedHashSet<T>(mapCapacity(size + 1))
    result.addAll(this)
    result.add(element)
    return result
}

minusKey():
返回一个上下文,其中包含该上下文中的元素,但不包含指定key入参的元素。

+ / [] / minusKey()的使用

示例一:

val context: CoroutineContext 
= Job() + Dispatchers.Default + CoroutineName("myContext")
log("$context")//[JobImpl{Active}@48d8d62, CoroutineName(myContext), Dispatchers.Default]
log("${context[CoroutineName]}")//CoroutineName(myContext)
log("${context.minusKey(CoroutineName)}")//[JobImpl{Active}@48d8d62, Dispatchers.Default]


示例二:

val coroutineContext =
    SupervisorJob() + Dispatchers.Default + CoroutineName("myContext") + CoroutineExceptionHandler { _, _ -> } 
//[SupervisorJobImpl{Active}@40019f1, CoroutineName(myContext),TestActivity$coroutineContextTest$$inlined$CoroutineExceptionHandler@d58a9d6, Dispatchers.Default]
log("$coroutineContext")

Element层级关系:

示例三:

val context = Dispatchers.Default + CoroutineName("myContext")
log("$context")//[CoroutineName(myContext), Dispatchers.Default]

//加号右侧的元素会覆盖加号左侧的元素,组成新创建的CoroutineContext
//覆盖Dispatchers.Default
val newContext = context + Dispatchers.IO
log("$newContext")//[CoroutineName(myContext), Dispatchers.IO]

//CoroutineName被覆盖
val newContext2 = newContext + CoroutineName("newContext2")
log("$newContext2")//[CoroutineName(newContext2), Dispatchers.IO]

加号右侧的元素会覆盖加号左侧的元素,组成新创建的CoroutineContext

PS:EmptyCoroutineContext 对象不持有任何元素。

Job

负责管理协程的生命周期,通过job可以获取到任务的状态。

public interface Job : CoroutineContext.Element {
//Key是单例的
    public companion object Key : CoroutineContext.Key<Job> {
        init {
            CoroutineExceptionHandler
        }
    }

    // ------------ state query ------------
    public val isActive: Boolean

    public val isCompleted: Boolean

    public val isCancelled: Boolean

    public fun getCancellationException(): CancellationException

    // ------------ state update ------------
    public fun start(): Boolean

    public fun cancel(cause: CancellationException? = null)

    public fun cancel(): Unit = cancel(null)

    public fun cancel(cause: Throwable? = null): Boolean

    // ------------ parent-child ------------
    public val children: Sequence<Job>

    public fun attachChild(child: ChildJob): ChildHandle

    // ------------ state waiting ------------
    public suspend fun join()

    public val onJoin: SelectClause0
}

start(): 调用该函数来启动这个 Coroutine(协程),如果当前 Coroutine 还没有执行调用该函数返回 true,如果当前 Coroutine 已经执行或者已经执行完毕,则调用该函数返回 false

cancel(): 使用可选的取消[原因]取消此作业。原因可用于指定错误消息或提供有关取消原因的其他详细信息,以便进行调试。

invokeOnCompletion(): 通过这个函数可以给 Job 设置一个完成通知,当 Job 执行完成的时候会同步执行这个通知函数。
CompletionHandler 参数代表了 Job 是如何执行完成的。 cause 有下面三种情况:

如果 Job 是正常执行完成的,则 cause 参数为 null如果 Job 是正常取消的,则 cause 参数为 CancellationException 对象。这种情况不应该当做错误处理,这是任务正常取消的情形。所以一般不需要在错误日志中记录这种情况。其他情况表示 Job 执行失败了。

这个函数的返回值为 DisposableHandle 对象,如果不再需要监控 Job 的完成情况了, 则可以调用 DisposableHandle.dispose 函数来取消监听。如果 Job 已经执行完了, 则无需调用 dispose 函数了,会自动取消监听。

join(): join 函数和前面三个函数不同,这是一个 suspend 函数。所以只能在 Coroutine 内调用。
这个函数会暂停当前所处的 Coroutine直到该Coroutine执行完成。所以 Job 函数一般用来在另外一个 Coroutine 中等待 job 执行完成后继续执行。
当 Job 执行完成后, job.join 函数恢复,这个时候 job 这个任务已经处于完成状态了,而调用 job.join 的Coroutine还继续处于 activie 状态。

请注意,只有在其所有子级都完成后,作业才能完成

Job的挂起是可以被取消的。如果在调用此挂起函数时或在挂起时调用协程的 [Job] 被取消或完成,则此函数将抛出 [CancellationException]。

Deferred

跟job区别:Deferred里面有挂起函数await()
public suspend fun await(): T (类似java的Future)
用来等待这个Coroutine执行完毕并返回结果。

public interface Deferred<out T> : Job {
	public suspend fun await(): T
	public fun getCompleted(): T
}
suspend关键字

suspend标志一个挂起函数,在挂起函数执行完成之后,协程会重新切回(resume)它原来的线程。
挂起之后的恢复是在协程里面执行的,所以挂起(suspend)函数必须在协程里被调用。

注意: 只使用suspend关键字并没有真正挂起这个任务,还需要调用Kotlin的suspend函数,否则这个关键字是没有意义的。

fun suspendTest() {
    runBlocking {
        launch { doWorld() }
        log("Hello")
        delay(2000L)
        log("runBlocking finish")
    }
}

//这是一个挂起函数
suspend fun doWorld() {
    delay(1000L)
    log("doWorld finish")
}

打印:

<20:35:11>  <main>  Hello
<20:35:12>  <main>  doWorld finish
<20:35:13>  <main>  runBlocking finish

doWorld()是一个耗时 *** 作,当主线程执行到doWorld时,并不会等待doWorld方法的返回,会越过doWorld继续执行。这时候doWorld会被压入栈里执行,当执行完成之后,主线程再回来挂起点继续执行。

CoroutineDispatcher:

它控制了协程以何种策略分配到哪些线程上运行。这里介绍几种常见的调度器:

Dispatchers.Default
适合处理后台计算,是一个CPU密集型任务调度器。如果创建 Coroutine 的时候没有指定 dispatcher,则一般默认使用这个作为默认值。Default dispatcher 使用一个共享的后台线程池来运行里面的任务。注意它和IO共享线程池,只不过限制了最大并发数不同。
内部是通过java线程池实现的。

Dispatchers.IO
顾名思义这是用来执行阻塞 IO *** 作的,是和Default共用一个共享的线程池来执行里面的任务。根据同时运行的任务数量,在需要的时候会创建额外的线程,当任务执行完毕后会释放不需要的线程。

Dispatchers.Unconfined
由于Dispatchers.Unconfined未定义线程池,所以执行的时候默认在启动线程。遇到第一个挂起点,之后由调用resume的线程决定恢复协程的线程。

Dispatchers.Main:
指定执行的线程是主线程,在Android上就是UI线程·

由于子Coroutine 会继承父Coroutine 的 context,所以为了方便使用,我们一般会在 父Coroutine 上设定一个 Dispatcher,然后所有 子Coroutine 自动使用这个 Dispatcher

CoroutineStart:(协程四种启动模式)

CoroutineStart.DEFAULT:
协程创建后立即开始调度,在调度前如果协程被取消,其将直接进入取消响应的状态
虽然是立即调度,但也有可能在执行前被取消

CoroutineStart.ATOMIC:
协程创建后立即开始调度,协程执行到第一个挂起点之前不响应取消
虽然是立即调度,但其将调度和执行两个步骤合二为一了,就像它的名字一样,其保证调度和执行是原子 *** 作,因此协程也一定会执行

CoroutineStart.LAZY:
只要协程被需要时,包括主动调用该协程的start、join或者await等函数时才会开始调度,如果调度前就被取消,协程将直接进入异常结束状态

CoroutineStart.UND ISPATCHED:
协程创建后立即在当前函数调用栈中执行,直到遇到第一个真正挂起的点
是立即执行,因此协程一定会执行

lazy测试:

fun start() {
    log("start ....")
    val launchJob =
        GlobalScope.launch(start = CoroutineStart.LAZY) {
            delay(1000L)
            log("launch 启动一个协程")
        }
    launchJob.start()
    Thread.sleep(1000L)
    log("launchJob = $launchJob")
}

打印:

<20:30:28>  <main>  start ....
<20:30:29>  <main>  launchJob = LazyStandaloneCoroutine{Active}@57b1d98
<20:30:29>  <DefaultDispatcher-worker-1>  launch 启动一个协程

备注: 间隔一秒后同时打印俩日志。

fun start() {
    log("start ....")
    val launchJob =
        GlobalScope.launch(context = Dispatchers.Main, start = CoroutineStart.LAZY) {
            delay(1000L)
            log("launch 启动一个协程")
        }
    launchJob.start()
    Thread.sleep(1000L)
    log("launchJob = $launchJob")
}

打印:

<20:31:32>  <main>  start ....
<20:31:33>  <main>  launchJob = LazyStandaloneCoroutine{Active}@5e7270a
<20:31:34>  <main>  launch 启动一个协程

注: 指定了context = Dispatchers.Main,是排队在主线程依次执行的。
后打印的日志时间都间隔前边的日志1秒

CoroutineScope: 协程作用域
public interface CoroutineScope {
    public val coroutineContext: CoroutineContext
}

CoroutineScope 只是定义了一个新 Coroutine 的执行 Scope。每个“coroutine builder”(如[launch]、[async]等)都是 CoroutineScope 的扩展函数,并且自动的继承了当前 Scope 的 CoroutineScope.coroutineContext。

分类及行为规则
官方框架在实现复合协程的过程中也提供了作用域,主要用以明确协程之间的父子关系,以及对于取消或者异常处理等方面的传播行为。该作用域包括以下三种:

顶级作用域
没有父协程的协程所在的作用域为顶级作用域。

协同作用域
协程中启动新的协程,新协程为所在协程的子协程,这种情况下,子协程所在的作用域默认为协同作用域。此时子协程抛出的未捕获异常会向上传播,传递给父协程处理,父协程同时也会被取消。任何一个子协程的异常退出,都会导致整体的退出。

//是挂起函数,需要运行在协程内或挂起函数内
public suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return suspendCoroutineUninterceptedOrReturn { uCont ->
        val coroutine = ScopeCoroutine(uCont.context, uCont)
        coroutine.startUndispatchedOrReturn(coroutine, block)
    }
}

主从作用域
与协同作用域在协程的父子关系上一致,区别在于,处于该作用域下的协程出现未捕获的异常时,不会将异常向上传递给父协程。

supervisorScope属于主从作用域,会继承父协程的上下文,它的特点就是子协程的异常不会影响父协程

//是挂起函数,需要运行在协程内或挂起函数内
public suspend fun <R> supervisorScope(block: suspend CoroutineScope.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return suspendCoroutineUninterceptedOrReturn { uCont ->
        val coroutine = SupervisorCoroutine(uCont.context, uCont)
        coroutine.startUndispatchedOrReturn(coroutine, block)
    }
}

除了三种作用域中提到的行为以外,父子协程之间还存在以下规则:

父协程手动调用cancel()或者异常结束,会立即取消它的所有子协程。由于协同作用域和主从作用域中都存在父子协程关系,因此此条规则都适用。父协程需要等待子协程执行完毕之后才会最终进入完成状态,不管父协程自身的协程体是否已经执行完。子协程会继承父协程的协程上下文中的元素,如果自身有相同key的成员,则覆盖对应的key,覆盖的效果仅限自身范围内有效。 在Android推荐自定义作用域来使用

指定作用域(SupervisorJob:主从作用域)和线程(Main/IO):

//指定作用域(SupervisorJob:主从作用域)和线程(Main)
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
private val scopeIO = CoroutineScope(SupervisorJob() + Dispatchers.IO)

绑定生命周期:

override fun onDestroy() {
    super.onDestroy()
    scope.cancel()
   scopeIO.cancel()
}

调用:

scope.launch {
    log("top level launch")
    scopeIO.launch {
        log("IO level launch")
    }
    scope.launch(Dispatchers.IO) {
        log("1 level launch")
        launch(Dispatchers.Unconfined) {
            log("2 level launch")
            launch(Dispatchers.Main) {
                log("3 level launch")
            }
        }
        log("end ..... 1 level launch")
    }
    log("end ..... top level launch")
}

打印:

<20:10:21>  <main>  top level launch
<20:10:21>  <DefaultDispatcher-worker-1>  IO level launch
<20:10:21>  <main>  end ..... top level launch
<20:10:21>  <DefaultDispatcher-worker-3>  1 level launch
<20:10:21>  <DefaultDispatcher-worker-3>  2 level launch
<20:10:21>  <main>  3 level launch
<20:10:21>  <DefaultDispatcher-worker-3>  end ..... 1 level launch

PS:也可以直接用MainScope

private val mainScope = MainScope()
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)

调用改进(withContext):
withContext {}不会创建新的协程,在指定协程上运行挂起代码块,并挂起当前协程直至指定协程的代码块运行完成。

//private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
scope.launch {
    //主线程执行  打印:[main] top level launch
    log("top level launch")
    //withContext {}不会创建新的协程,在指定协程上运行挂起代码块,并挂起该协程直至代码块运行完成
    withContext(Dispatchers.IO) {
        //打印:[DefaultDispatcher-worker-1] 切换线程到 IO
        log("切换线程到 IO")
    }
    //打印:[main] 切换到IO后。。。。
    log("切换到IO后。。。。")

    withContext(Dispatchers.Default) {
        //default:上一个协程块在哪里执行的,就在哪执行
        //打印:[DefaultDispatcher-worker-1] 切换线程到 Default
        log("切换线程到 Default")
    }
    //打印:[main] 切换到IDefault后。。。。
    log("切换到IDefault后。。。。")
    //打印:[main] end ..... top level launch
    log("end ..... top level launch")
}

四个举例:

suspend fun doSomeThing() {
    delay(1000)
    log("doSomeThing")
}

suspend fun doOtherThing() {
    delay(2000)
    log("doOtherThing")
}

fun test1() {
    scope.launch(Dispatchers.Main) {
        log("start ....")
        doSomeThing()
        doOtherThing()
        log("end .....")
    }
}

打印:

<18:32:13>  <main>  start ....
<18:32:14>  <main>  doSomeThing
<18:32:16>  <main>  doOtherThing
<18:32:16>  <main>  end .....

顺序执行,差2秒。

fun test2() {
    scope.launch(Dispatchers.Main) {
        log("start ....")
        withContext(Dispatchers.IO) {
            doSomeThing()
        }
        withContext(Dispatchers.IO) {
            doOtherThing()
        }
        log("end .....")
    }
}

打印:

<18:35:06>  <main>  start ....
<18:35:07>  <DefaultDispatcher-worker-1>  doSomeThing
<18:35:09>  <DefaultDispatcher-worker-1>  doOtherThing
<18:35:09>  <main>  end .....

也是顺序执行,差2S。

fun test3() {
    scope.launch(Dispatchers.Main) {
        log("start ....")
        withContext(Dispatchers.IO) {
            doSomeThing()
        }
        doOtherThing()
        log("end .....")
    }
}

打印:

<18:38:33>  <main>  start ....
<18:38:34>  <DefaultDispatcher-worker-1>  doSomeThing
<18:38:36>  <main>  doOtherThing
<18:38:36>  <main>  end .....

顺序执行。同时withContext也不会创建新协程,差2S

fun test4() {
    scope.launch(Dispatchers.Main) {
        log("start ....")
//launch新创建了协程,不会影响主线程,所以先打印end... (withContext不会新创建)
        launch {
        doSomeThing()
        }
        launch {
            doOtherThing()
        }
        log("end .....")
    }
}

打印:

<18:44:52>  <main>  start ....
<18:44:52>  <main>  end .....
<18:44:53>  <main>  doSomeThing
<18:44:54>  <main>  doOtherThing

launch创建的协程不影响主线程。

launch{}=>
public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}
Activity实现CoroutineScope,就可以直接用launch{},调用就是MainScope
class DemoActivity : AppCompatActivity(), CoroutineScope {
    private val job by lazy {
        SupervisorJob()
    }
    override fun onDestroy() {
        job.cancel()
    }
    override val coroutineContext: CoroutineContext
        get() = job + Dispatchers.Main
}

PS:也可以 class MainActivity : AppCompatActivity() , CoroutineScope by MainScope(){

jetpack + 协程 ViewModel
interface GitHubApi {
    @GET("/users/{user}/repos")
    suspend fun listReposSuspend(@Path("user") user: String): List<Repo>
}
class MyViewModel : ViewModel(){
    //获取github仓库信息
     fun getRepos(){
        val retrofit = Retrofit.Builder()
            .baseUrl(HttpUrl.get("https://api.github.com/"))
            .addConverterFactory(GsonConverterFactory.create())
            .build()

        val gitHubApi = retrofit.create(GitHubApi::class.java)

        viewModelScope.launch {//viewmodel生命周期销毁后会自动取消网络请求
           val repos =  gitHubApi.listReposSuspend("xxxx")
            log("repos: ${repos.size}")
        }
    }
}

ViewmoModel.kt

//当ViewModel被清除时,此作用域将被取消
public val ViewModel.viewModelScope: CoroutineScope
    get() {
        val scope: CoroutineScope? = this.getTag(JOB_KEY)
        if (scope != null) {
            return scope
        }
        return setTagIfAbsent(
            JOB_KEY,
            CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
        )
    }
Lifecycle
class MyFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewLifecycleOwner.lifecycleScope.launch {
            whenCreated {
				//不同生命周期执行不同逻辑
            }
            whenResumed {
            }
        }
    }
}
旧代码改用协程

方式一:(不优雅)

//方式一
fun suspendTest() {
    log("start ....")
    scope.launch {
        val result = scopeIO.async { paserJson1() }.await()
        log(result)//主线程了
    }
}

fun paserJson1(): String {
    Thread.sleep(2000)
    return "xxxxx"
}

打印:

<19:34:16>  <main>  start ....
<19:34:18>  <main>  xxxxx

方式二:(优雅的方式)

fun suspendTest1() {
    scope.launch {
        log("start ....")
        val result = paserJson2()
        log(result)//主线程
    }
}
//方式二
suspend fun paserJson2(): String {
    return suspendCancellableCoroutine<String> { continuation ->
        Thread {
	//子线程
            log("开始执行任务。。。")
            Thread.sleep(2000)
            log("执行完了。。。 ")
            continuation.resumeWith(Result.success("suspend xxxxxxx"))
            log("结果返回了") 
        }.start()
    }
}

打印:

<19:35:22>  <main>  start ....
<19:35:22>  <Thread-17>  开始执行任务。。。
<19:35:24>  <Thread-17>  执行完了。。。 
<19:35:24>  <Thread-17>  结果返回了
<19:35:24>  <main>  suspend xxxxxxx
其它
//在不阻塞线程的情况下,将协程延迟一段指定的时间后再恢复。
// delay 是一个特殊的 挂起函数 ,它不会造成线程阻塞,但是会 挂起 协程,并且只能在协程中使用。
public suspend fun delay(timeMillis: Long) {
    if (timeMillis <= 0) return // don't delay
    return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
        // if timeMillis == Long.MAX_VALUE then just wait forever like awaitCancellation, don't schedule.
        if (timeMillis < Long.MAX_VALUE) {
            cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
        }
    }
}

PS:代码中CoroutineScope 有两个,一个是接口,一个是函数:

//接口CoroutineScope
public object GlobalScope : CoroutineScope {
    override val coroutineContext: CoroutineContext
        get() = EmptyCoroutineContext
}
//函数CoroutineScope 
@Suppress("FunctionName")
public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
    ContextScope(if (context[Job] != null) context else context + Job())

举例用的log方法:

private fun log(log: String) {
    println("<${SimpleDateFormat("HH:mm:ss").format(System.currentTimeMillis())}>  <${Thread.currentThread().name}>  $log")
}
参考文章

https://www.kotlincn.net/docs/reference/coroutines/basics.html
https://blog.csdn.net/zou8944/article/details/106447727
https://zhuanlan.zhihu.com/p/427092689

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/992615.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存