Kotlin协程,看完包你明白

对协程的理解

协程,英文名是 Coroutine, 协程是基于编译器的,在一个线程中可以创建多个协程,通过挂起函数来实现协程内的代码块不管是异步还是同步都能顺序执行。

Gradle中导入kotlin协程的依赖

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3'

启动协程的方法

我们先来看看协程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
}
  • 协程上下文
    协程上下文用CoroutineContext表示,kotlin中 比较常用的launch方法返回值(job)、协程调度器(CoroutineDispatcher)、协程拦截器(ContinuationInterceptor)等都是实现了CoroutineContext的类,即它们都是协程上下文

  • 协程调度器
    协程调度器功能上是类似于RxJava的线程调度器,用来指定协程代码块在哪个线程中执行。kotlin提供了几个默认的协程调度器,分别是DefaultMainUnconfined
    1、Default
    指定代码块在默认线程池中执行

GlobalScope.launch(Dispatchers.Default) {
   println("print1: " + Thread.currentThread().name)
   launch (Dispatchers.Default) {
       delay(2000) // 延迟2秒
       println("print2: " + Thread.currentThread().name)
   }
   println("print3: " + Thread.currentThread().name)
}

打印结果如下

print1: DefaultDispatcher-worker-1
print3: DefaultDispatcher-worker-1
print2: DefaultDispatcher-worker-1

2、Main
指定代码块在主线程中执行(在Android中就是ui线程)

GlobalScope.launch(Dispatchers.Default) {
    println("print1: " + Thread.currentThread().name)
    launch (Dispatchers.Main) {
        delay(2000) // 延迟2秒
        println("print2: " + Thread.currentThread().name)
    }
    println("print3: " + Thread.currentThread().name)
}

打印结果如下

print1: DefaultDispatcher-worker-1
print3: DefaultDispatcher-worker-1
print2: main

3、Unconfined
代表不指定线程,则在当前线程中执行

GlobalScope.launch(Dispatchers.Default) {
    println("print1: " + Thread.currentThread().name)
    launch (Dispatchers.Unconfined) {
    	delay(2000) // 延迟2秒
   		println("print2: " + Thread.currentThread().name)   
    }
    println("print3: " + Thread.currentThread().name)
}

打印结果如下

print1: DefaultDispatcher-worker-1
print3: DefaultDispatcher-worker-1
print2: DefaultDispatcher-worker-1
  • 协程启动模式
    1、DEFAULT
    立即执行协程体
GlobalScope.launch(start = CoroutineStart.DEFAULT) {
	println("1: " + Thread.currentThread().name)
}

2、LAZY
手动调用启动方法来执行协程体

val job = GlobalScope.launch(start = CoroutineStart.LAZY) {
	println("1: " + Thread.currentThread().name)
}
// 调用join方法来执行协程体,不然协程体不会执行
job.join()

3、ATOMIC
立即执行协程体,而且协程体中除了挂起函数,都会无视cancel状态

val job = GlobalScope.launch(start = CoroutineStart.ATOMIC) {
        println("start")
        var count = 0
        for(i in 0 until 10000){
        	count++
        }
        println("end: " + count)
    }
job.cancel()

打印结果如下

start
end: 10000

如果协程体中调用了挂起函数

val job = GlobalScope.launch(start = CoroutineStart.ATOMIC) {
	println("start")
	var count = 0
    for(i in 0 until 10000){
      	count++
    }
    delay(2000) //执行延迟两秒的挂起函数
    println("end: " + count)
}
job.cancel()

打印结果如下

start

实例1中就算调用了cancel方法,但是ATOMIC模式启动可以无视cancel状态,所以协程体能全部执行,实例2中因为协程体内有挂起函数,执行挂起函数时发现当前协程体处于cancel状态,就抛出了JobCancellationException异常,导致后续代码不能执行,如果使用trycatch包裹delay方法,那么后续代码也会继续执行

4、UNDISPATCHED
立即在当前线程中执行协程体,直到调用了第一个挂起函数,挂起函数之后的代码执行的线程就取决当前协程体的上下文线程调度器(GlobalScope除外,因为GlobalScope.launch启动的是一个顶级协程,无法关联当前协程的上下文)

runBlocking {
    println("print0: " + Thread.currentThread().name)
    launch(context = Dispatchers.Default, start = CoroutineStart.UNDISPATCHED) {
        println("print1: " + Thread.currentThread().name)
        delay(2000)//延迟2秒
        println("print2: " + Thread.currentThread().name)
    }
}

打印结果如下

print0: main
print1: main
print2: DefaultDispatcher-worker-1
  • 挂起函数
    suspend关键字来修饰一个方法,表示该方法是个挂起函数。挂起函数只能在协程中使用。
    我们看看Retrofit中对协程的兼容:
suspend fun <T : Any> Call<T>.await(): T {
	// 使用suspendCancellableCoroutine定义挂起函数,参数是Continuation对象
	return suspendCancellableCoroutine { continuation ->
    continuation.invokeOnCancellation {
      cancel()
    }
    enqueue(object : Callback<T> {
      override fun onResponse(call: Call<T>, response: Response<T>) {
        if (response.isSuccessful) {
          val body = response.body()
          if (body == null) {
            val invocation = call.request().tag(Invocation::class.java)!!
            val method = invocation.method()
            val e = KotlinNullPointerException("Response from " +
                method.declaringClass.name +
                '.' +
                method.name +
                " was null but response body type was declared as non-null")
            // 如果结果异常,则调用Continuation 的 resumeWithException回调
            continuation.resumeWithException(e)
          } else {
          	// 如果结果正常,则调用Continuation 的 resume回调
            continuation.resume(body)
          }
        } else {
          // 如果结果异常,则调用Continuation 的 resumeWithException回调
          continuation.resumeWithException(HttpException(response))
        }
      }

      override fun onFailure(call: Call<T>, t: Throwable) {
        // 如果结果异常,则调用Continuation 的 resumeWithException回调
        continuation.resumeWithException(t)
      }
    })
  }
}

Retrofit2内部扩展了Call对象的一个await()方法,在方法中返回了一个suspendCancellableCoroutine,然后在Continuation类型参数的闭包中执行了Retrofit2Call对象的enqueue方法去执行对应的网络请求,然后在Callback<T>回调中,如果成功,则调用continuation.resume()方法,如果失败或者出现异常,则调用continuation.resumeWithException()
Continuation的源码和扩展函数如下:

@SinceKotlin("1.3")
@InlineOnly
public inline fun <T> Continuation<T>.resume(value: T): Unit =
    resumeWith(Result.success(value))

@SinceKotlin("1.3")
@InlineOnly
public inline fun <T> Continuation<T>.resumeWithException(exception: Throwable): Unit =
    resumeWith(Result.failure(exception))

@SinceKotlin("1.3")
public interface Continuation<in T> {
    public val context: CoroutineContext
    
    public fun resumeWith(result: Result<T>)
}

我们可以清楚的看到,Continuation是一个接口,然后扩展了resume()resumeWithException()方法,然后在不同的地方分别调用。
可见协程挂起函数内部,还是使用的回调的方式将结果返回出去,当有正确结果时,Continuation 调用 resume()返回最终的结果,不然调用 resumeWithException() 来返回错误信息。
而我们在AS中写协程代码的挂起函数时,看起来是同步执行,其实是编译器帮我们做了很多其他的事情,而我们只需要用trycatch来获得不同的结果。

Retrofit2中使用协程

注意Retrofit2在2.6.0才开始支持协程,所以一定要升级到2.6.0及以上,并在Retrofit初始化时设置适配协程的callAdapter

addCallAdapterFactory(CoroutineCallAdapterFactory())

先分别定义两个api,一个是结合rxjava2的用法,一个结合协程的用法,经过上面的讲解,我们知道了Retroifit2使用suspend挂起函数来定义请求的方法

interface TestService {
    @GET("api/product/list")
    fun getProductList(): Flowable<ProductList>
    
    @GET("api/product/list")
    suspend fun getProductList2(): ProductList
}

fragment中分别调用两个请求方法

class TestFragment : Fragment() {
	...
	// 使用RxJava
    fun request1() {
        testService.getProductList()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(object : DisposableSubscriber<ProductList>() {
                override fun onComplete() {}

                override fun onNext(t: ProductList) {
                    tv.text = Gson().toJson(t)
                }

                override fun onError(t: Throwable?) {
                    tv.text = "error"
                }
            })
    }
    
    // 使用协程
    fun request2() {
        GlobalScope.launch(Dispatchers.Main) {
            try {
                tv.text = Gson().toJson(testService.getProductList2())
            } catch (e: Exception) {
                tv..text = "error"
            }
        }
    }
}

协程请求数据的代码是不是简化了很多,没有回调,只使用trycatch来捕获异常,而且看起来还是同步的样子。
我们再发起串行和并发的请求试试。

interface TestApi {
	@GET("api/product/list")
    fun getProductList(): Flowable<ProductList>

    @GET("api/product/{id}")
    fun getProductDetail(@Path("id") id: String): Flowable<ProductDetail>


    @GET("api/product/list")
    suspend fun getProductList2(): ProductList

    @GET("api/product/{id}")
    suspend fun getProductDetail2(@Path("id") id: Long): ProductDetail
}

我们先调用getProductList()方法请求商品列表,然后再调用getProductDetail()方法,传入商品列表中第一个商品的id来获取商品详情,代码如下

// RxJava
testService.getProductList()
    .flatMap {
        testService.getProductDetail(it.list!![0].id!!)
    }
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(object : DisposableSubscriber<ProductDetail>() {
        override fun onComplete() {}

        override fun onNext(t: ProductDetail) {
            tv.text = t.name
        }

        override fun onError(t: Throwable?) {
            tv.text = "error"
        }
    })
    
// 协程
GlobalScope.launch(Dispatchers.Main) {
    try {
        val productList = testService.getProducList2()
        val detail = testService.getProductDetail2(productList.list!![0].id!!)
        tv.text = detail.name
    } catch(e: Exception) {
        tv.text = "error"
    }
}

如果我们想调用getProductDetail同时请求多个商品详情

// RxJava
testService.getProductList()
    .flatMap {
        Flowable.zip(
            testService.getProductDetail(it.list!![0].id!!), 
            testService.getProductDetail(it.list!![1].id!!), 
            BiFunction<ProductDetail, ProductDetail, List<ProductDetail>> { detail1, detail2->
                listOf(detail1, detail2) 
            }
        )
    }
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(object : DisposableSubscriber<List<ProductDetail>>() {
        override fun onComplete() {}

        override fun onNext(t: List<News>) {
            tv.text = t[0].name + t[1].name
        }

        override fun onError(t: Throwable?) {
            tv.text = "error"
        }
    })

// 协程的用法
GlobalScope.launch(Dispatchers.Main) {
    try {
        val productList = testService.getProductList2()
        // 再使用 async 并发请求第一个和第二个商品的详情
        val detail1 = async { 
        	testService.getProductDetail2(productList.list!![0].id!!) 
        }
        val detail2 = async {
        	testService.getProductDetail2(productList.list!![1].id!!) 
        }
        // await()拿到结果
        tv.text = detail1.await().name + detail2.await().name
    } catch(e: Exception) {
        tv.text = "error"
    }
}

Retrofit2适配协程的adapter在内部也已经帮我们实现了并发的请求,我们可以这样来定义请求方法:

@GET("api/product/{id}")
fun getProductDetail3(@Path("id") id: Long): Deferred<ProductDetail>

然后这样使用:

GlobalScope.launch(Dispatchers.Main) {
   try {
       val productList = testService.getProductList2()
       // 直接串行调用请求方法,不需要使用 async 包裹,内部已经帮我们实现了并发请求
       val detail1 = testService.getProductDetail3(productList.list!![0].id!!)
       val detail2 = testService.getProductDetail3(productList.list!![1].id!!) 
       // await()拿到结果
       tv.text = detail1.await().name + detail2.await().name
   } catch(e: Exception) {
       tv.text = "error"
   }
}

看起来相比于回调的形式,协程能让代码更加清晰,能一目了然地看出第一步想做什么、第二步想做什么。

协程在Android的使用

在上面的例子中,我们都是通过GlobalScope来启动协程,但是在安卓中是不推荐使用GlobalScope的,因为GlobalScope启动的都是顶级协程,消耗的资源相对来说比较大,而且每个协程都是独立的,不和其他协程的上下文所关联。所以如果忘记了持有某个GlobalScope的引用,没有在ActivityFragmentonDestroyonDestroyViewcancel该协程,那么就有可能引起内存泄露等常见的问题

所以在安卓中使用GlobalScope的正确姿势:

class TestFragment : Fragment() {
    private lateinit var testService: TestService
    private var job1: Job? = null
    private var job2: Job? = null
    
    ...

    override fun onDestroyView() {
        super.onDestroyView()
        job1?.cancel()
        job2?.cancel()
    }
    ...
    
    // 启动第一个顶级协程
    fun request1() {
        job1 = GlobalScope.launch(Dispatchers.Main) {
            try {
                val productList = testService.getProductList2()
                tv.text = Gson().toJson(productList)
            } catch(e: Exception) {
                tv.text = "error"
            }
        }
    }

	// 启动第二个顶级协程
    fun requestData2() {
        job2 = GlobalScope.launch(Dispatchers.Main) {
            try {
                val productList = testService.getProductList2()
                tv.text = Gson().toJson(productList)
            } catch(e: Exception) {
                tv.text = "error"
            }
        }
    }
}

可见如果使用GlobalScope启动的协程越多,就必须定义越多的变量持有对GlobalScope.launch的引用,并在onDestroy的时候cancel掉所有协程。
在安卓中推荐使用MainScope来代替GlobalScope
我们先看看MainScope的定义:

@Suppress("FunctionName")
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)

补充:+ 号在协程中其实是CoroutineContext自身的一个方法,该方法会把参数传入的CoroutineContext和调用对象CoroutineContext本身做一个组装,拼凑成一个新的CombinedContext对象,返回出来。

public operator fun plus(context: CoroutineContext): CoroutineContext =
        if (context === EmptyCoroutineContext) this else // fast path -- avoid lambda creation
            context.fold(this) { acc, element ->
                val removed = acc.minusKey(element.key)
                if (removed === EmptyCoroutineContext) element else {
                    // make sure interceptor is always last in the context (and thus is fast to get when present)
                    val interceptor = removed[ContinuationInterceptor]
                    if (interceptor == null) CombinedContext(removed, element) else {
                        val left = removed.minusKey(ContinuationInterceptor)
                        if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
                            CombinedContext(CombinedContext(left, element), interceptor)
                    }
                }
            }

可见MainScope的调度器是Dispatchers.Main,执行在ui线程,而retrofit2内部在执行挂起函数时切换到了子线程去执行网络请求,所以我们不需要去关心线程的切换

class TestFragment : Fragment() {
    private var mainScope = MainScope()
    private lateinit var testService: TestService
    
    ...

    override fun onDestroyView() {
        super.onDestroyView()
        // 只需要调用mainScope.cancel(),就会cancel掉所有使用mainScope启动的协程
        mainScope.cancel()
    }

    fun request1() {
        mainScope.launch {
            try {
                val productList = testService.getProductList2()
                tv.text = Gson().toJson(productList)
            } catch(e: Exception) {
                tv.text = "error"
            }
        }
    }

    fun request2() {
        mainScope.launch {
            try {
                val productList = testService.getProductList2()
                val detail = testApi.getProductDetail2(productList.list!![0].id!!)
                tv.text = detail.name
            } catch (e: Exception) {
                tv.text = "error"
            }
        }
    }
}

也可以使用kotlin的委托模式,就显得更加简洁了,代码如下:

class TestFragment : TestFragment(), CoroutineScope by MainScope() {
    private lateinit var testApi: TestApi

	...
	
    override fun onDestroyView() {
        super.onDestroyView()
        cancel()
    }

    fun requestData1() {
        launch {
            try {
                val productList = testService.getProductList2()
                tv.text = Gson().toJson(productList)
            } catch(e: Exception) {
                tv.text = "error"
            }
        }
    }

    fun requestData2() {
        launch {
            try {
                val productList = testService.getProductList2()
                val detail = testApi.getProductDetail2(productList.list!![0].id!!)
                tv.text = detail.name
            } catch (e: Exception) {
                tv.text = "error"
            }
        }
    }
}

如果是MVP架构,也可以在presenter中通过委托模式使用MainScope来开启协程。

Lifecycle对协程的支持

添加最新的依赖:
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-rc03"

lifecycle-runtime-ktx 中 给LifecycleOwner添加了继承于LifecycleCoroutineScopelifecycleScope扩展属性,

我们来看看Lifecycle,源码如下:

val Lifecycle.coroutineScope: LifecycleCoroutineScope
    get() {
        while (true) {
            val existing = mInternalScopeRef.get() as LifecycleCoroutineScopeImpl?
            if (existing != null) {
                return existing
            }
            val newScope = LifecycleCoroutineScopeImpl(
                this,
                SupervisorJob() + Dispatchers.Main.immediate
            )
            if (mInternalScopeRef.compareAndSet(null, newScope)) {
                newScope.register()
                return newScope
            }
        }
    }

abstract class LifecycleCoroutineScope internal constructor() : CoroutineScope {
    internal abstract val lifecycle: Lifecycle

   // 当 activity 处于create的时候执行协程体
    fun launchWhenCreated(block: suspend CoroutineScope.() -> Unit): Job = launch {
        lifecycle.whenCreated(block)
    }

    // 当 activity 处于start的时候执行协程体
    fun launchWhenStarted(block: suspend CoroutineScope.() -> Unit): Job = launch {
        lifecycle.whenStarted(block)
    }

    // 当 activity 处于resume的时候执行协程体
    fun launchWhenResumed(block: suspend CoroutineScope.() -> Unit): Job = launch {
        lifecycle.whenResumed(block)
    }
}

internal class LifecycleCoroutineScopeImpl(
    override val lifecycle: Lifecycle,
    override val coroutineContext: CoroutineContext
) : LifecycleCoroutineScope(), LifecycleEventObserver {
    init {
       //初始化时如果是destroy状态则取消协程
        if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
            coroutineContext.cancel()
        }
    }

    fun register() {
        launch(Dispatchers.Main.immediate) {
            if (lifecycle.currentState >= Lifecycle.State.INITIALIZED) {
                lifecycle.addObserver(this@LifecycleCoroutineScopeImpl)
            } else {
                coroutineContext.cancel()
            }
        }
    }

    override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
    	//当生命周期变化且destroy的时候取消协程
        if (lifecycle.currentState <= Lifecycle.State.DESTROYED) {
            lifecycle.removeObserver(this)
            coroutineContext.cancel()
        }
    }
}

所以如果想要在ActivityonResume生命周期中执行某个操作,可以直接调用:

class TestActivity : AppCompatActivity() {
    private lateinit var testService: TestService

	...

	override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test)
        request()
    }

    fun request() {
        lifecycleScope.launchWhenResumed {
            try {
                val productList = testService.getProductList2()
                tv.text = Gson().toJson(productList)
            } catch(e: Exception) {
                tv.text = "error"
            }
        }
    }
}

我们在onCreate中调用request()方法,会在Activity执行onResume时执行协程体,在onDestroy时取消协程

LiveData对协程的支持

添加最新的依赖:
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-rc03"

该依赖添加了顶级函数liveData(),返回CoroutineLiveData对象,源码如下:

@UseExperimental(ExperimentalTypeInference::class)
fun <T> liveData(
    context: CoroutineContext = EmptyCoroutineContext,
    timeoutInMs: Long = DEFAULT_TIMEOUT,
    @BuilderInference block: suspend LiveDataScope<T>.() -> Unit
): LiveData<T> = CoroutineLiveData(context, timeoutInMs, block)


internal class CoroutineLiveData<T>(
    context: CoroutineContext = EmptyCoroutineContext,
    timeoutInMs: Long = DEFAULT_TIMEOUT,
    block: Block<T>
) : MediatorLiveData<T>() {
    private var blockRunner: BlockRunner<T>?
    private var emittedSource: EmittedSource? = null

    init {     
        val supervisorJob = SupervisorJob(context[Job])
        val scope = CoroutineScope(Dispatchers.Main.immediate + context + supervisorJob)
        blockRunner = BlockRunner(
            liveData = this,
            block = block,
            timeoutInMs = timeoutInMs,
            scope = scope
        ) {
            blockRunner = null
        }
    }

    internal suspend fun emitSource(source: LiveData<T>): DisposableHandle {
        clearSource()
        val newSource = addDisposableSource(source)
        emittedSource = newSource
        return newSource
    }

    internal suspend fun clearSource() {
        emittedSource?.disposeNow()
        emittedSource = null
    }

    override fun onActive() {
        super.onActive()
        //在LifecycleOwner活跃时执行
        blockRunner?.maybeRun()
    }

    override fun onInactive() {
        super.onInactive()
        //在LifecycleOwner不活跃时取消(具体取消的是什么我们等会再说)
        blockRunner?.cancel()
    }
}

所以如果在Activity中调用:

class TestActivity : AppCompatActivity() {
    private lateinit var testApi: TestService
    
	...

    fun request() {
    	//调用liveData()方法
        liveData {
            try {
                val productList = testService.getProductList2()
                //把结果发射出去
                emit(Gson().toJson(productList))
            } catch(e: Exception) {
                emit("error")
            }
        }.observe(this, object : Observer<String>{
            override fun onChanged(t: String?) {
           		//observe第一个参数是LifecycleOwner,所以传this,表示该Activity,因为是LiveData,所以只会在Activity活跃时回调到onChanged方法
            	tv.text = it
            }
        })
    }
}

看起来确实跟LiveData一样,在活跃状态时才能接收到数据,那之前源码中不活跃时调用的cancel()方法,到底是取消的什么呢?难道我们Activity一进入不活跃状态时,就把协程给取消掉了吗?
我们再来看看blockRunnercancel()方法

@MainThread
fun cancel() {
 	if (cancellationJob != null) {
    	error("Cancel call cannot happen without a maybeRun")
	}
	cancellationJob = scope.launch(Dispatchers.Main.immediate) {
    	delay(timeoutInMs)
    	if (!liveData.hasActiveObservers()) {
        	runningJob?.cancel()
           	runningJob = null
        }
    }
}

cancel方法中执行的是一个cancellationJob协程,然后delaytimeoutInMs的时间,然后再去检查是否有活跃的观察者,如果还是没有的话,就会取消掉liveData()方法中执行的协程。所以可以看出来,在cancel方法,并不会马上就去取消掉当前的协程,而是会推迟,如果默认的时间内还没有进入活跃状态,才会取消,这就类似于一个超时的逻辑。timeoutInMs参数又是在CoroutineLiveData的构造方法里给赋了一个默认的值:

//默认超时时间为5秒
internal const val DEFAULT_TIMEOUT = 5000L

internal class CoroutineLiveData<T>(
    context: CoroutineContext = EmptyCoroutineContext,
    timeoutInMs: Long = DEFAULT_TIMEOUT,
    block: Block<T>
) 

所以cancel()方法什么时候起作用呢,举个例子,上代码:

liveData {
	try {
    	delay(3000) // 延迟3秒
		emit("123")
    } catch (t: Throwable) {
    	emit("error")
	}
}.observe(this, object : Observer<String>{
	override fun onChanged(t: String?) {
	Log.e("result", "$t")
	}
})

我们的协程体中需要3s才能执行完,然后我们在Activity一调用这个方法时,就按Home键回到桌面,注意不是回退键,过了10秒之后,再打开该Activity,当Activity变回活跃状态时发现执行了打印log的逻辑。
那么我们改变一下上面delay的时长:

liveData {
	try {
    	delay(10000) //延迟10秒
		emit("123")
    } catch (t: Throwable) {
    	emit("error")
	}
}.observe(this, object : Observer<String>{
	override fun onChanged(t: String?) {
	Log.e("result", "$t")
	}
})

然后我们重复上面的操作,10秒后当Activity变回活跃状态时发现并没有执行打印log的逻辑。
所以就印证了我们之前看到源码中的逻辑:

  • 操作1:在不活跃状态下3s内就执行完了协程体,并且发射出了结果给观察者,然后Activity不管多久之后回到活跃状态,都能获取到LiveData的值;
  • 操作2:在不活跃状态下默认的5秒超时时间内没有执行完协程体并把结果发射给观察者,那么blockRunner就会取消掉协程体,Activity回到活跃状态时就拿不到想要的结果。

我们在调用liveData()方法时可以自己传入自定义的超时时间,来改变超时取消的时长。

但是在测试过程当中,发现在liveData()的闭包中没有async方法,查看源码时才知道,原来asyncCoroutineScope的扩展方法,而liveData()参数中的闭包是实现了LiveDataScope接口的对象,而且并没有继承CoroutineScope,所以暂时在androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-rc03版本的liveData()中不能执行并发的请求,不知道是Google的bug还是另有其他的用意。所以暂时想要并发的使用协程而且搭配LiveData的话,可以这样写:

private val liveData = MutableLiveData<Int>()
private val mainScope = MainScope()

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    liveData.observe(this, Observer { it ->
        Log.e("测试", "LiveData : $it")
    })
    test()
}

fun test() {
  	mainScope.launch {
        val job1 = async {
            delay(3000)
                1
        }
        val job2 = async {
            delay(3000)
                2
        }
        val result = job1.await() + job2.await()
        liveData.value = result
    }
}

override fun onDestroy() {
    super.onDestroy()
    mainScope.cancel() 
}

总结

到这里,终于介绍完了Kotlin协程的一些基本原理和常见属性的含义,并且简单演示了协程在Android中的使用,希望大家看完之后对Kotlin协程有一个基本的认识,能够在项目中逐渐使用起来。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值