协程,英文名是 Coroutine
, 协程是基于编译器的,在一个线程中可以创建多个协程,通过挂起函数
来实现协程内的代码块不管是异步还是同步都能顺序执行。
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提供了几个默认的协程调度器,分别是Default
、Main
、Unconfined
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
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
异常,导致后续代码不能执行,如果使用try
、catch
包裹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 Call.await(): T {
// 使用suspendCancellableCoroutine定义挂起函数,参数是Continuation对象
return suspendCancellableCoroutine { continuation ->
continuation.invokeOnCancellation {
cancel()
}
enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
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: Throwable) {
// 如果结果异常,则调用Continuation 的 resumeWithException回调
continuation.resumeWithException(t)
}
})
}
}
在Retrofit2
内部扩展了Call
对象的一个await()
方法,在方法中返回了一个suspendCancellableCoroutine
,然后在Continuation类型参数的闭包中执行了Retrofit2
中Call
对象的enqueue
方法去执行对应的网络请求,然后在Callback
回调中,如果成功,则调用continuation.resume()
方法,如果失败或者出现异常,则调用continuation.resumeWithException()
。
Continuation
的源码和扩展函数如下:
@SinceKotlin("1.3")
@InlineOnly
public inline fun Continuation.resume(value: T): Unit =
resumeWith(Result.success(value))
@SinceKotlin("1.3")
@InlineOnly
public inline fun Continuation.resumeWithException(exception: Throwable): Unit =
resumeWith(Result.failure(exception))
@SinceKotlin("1.3")
public interface Continuation {
public val context: CoroutineContext
public fun resumeWith(result: Result)
}
我们可以清楚的看到,Continuation
是一个接口,然后扩展了resume()
和resumeWithException()
方法,然后在不同的地方分别调用。
可见协程挂起函数内部,还是使用的回调的方式将结果返回出去,当有正确结果时,Continuation
调用 resume()
返回最终的结果,不然调用 resumeWithException()
来返回错误信息。
而我们在AS中写协程代码的挂起函数时,看起来是同步执行,其实是编译器帮我们做了很多其他的事情,而我们只需要用try
、catch
来获得不同的结果。
注意Retrofit2
在2.6.0才开始支持协程,所以一定要升级到2.6.0及以上,并在Retrofit
初始化时设置适配协程的callAdapter
:
addCallAdapterFactory(CoroutineCallAdapterFactory())
先分别定义两个api,一个是结合rxjava2
的用法,一个结合协程
的用法,经过上面的讲解,我们知道了Retroifit2
使用suspend
挂起函数来定义请求的方法
interface TestService {
@GET("api/product/list")
fun getProductList(): Flowable
@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() {
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"
}
}
}
}
协程请求数据的代码是不是简化了很多,没有回调,只使用try
、catch
来捕获异常,而且看起来还是同步的样子。
我们再发起串行和并发的请求试试。
interface TestApi {
@GET("api/product/list")
fun getProductList(): Flowable
@GET("api/product/{id}")
fun getProductDetail(@Path("id") id: String): Flowable
@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() {
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> { detail1, detail2->
listOf(detail1, detail2)
}
)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : DisposableSubscriber>() {
override fun onComplete() {}
override fun onNext(t: List) {
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
然后这样使用:
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"
}
}
看起来相比于回调的形式,协程能让代码更加清晰,能一目了然地看出第一步想做什么、第二步想做什么。
在上面的例子中,我们都是通过GlobalScope
来启动协程,但是在安卓中是不推荐使用GlobalScope
的,因为GlobalScope
启动的都是顶级协程,消耗的资源相对来说比较大,而且每个协程都是独立的,不和其他协程的上下文所关联。所以如果忘记了持有某个GlobalScope
的引用,没有在Activity
和Fragment
的onDestroy
和onDestroyView
中cancel
该协程,那么就有可能引起内存泄露等常见的问题
所以在安卓中使用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)
可见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
来开启协程。
添加最新的依赖:
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-rc03"
lifecycle-runtime-ktx
中 给LifecycleOwner
添加了继承于LifecycleCoroutineScope
的lifecycleScope
扩展属性,
我们来看看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()
}
}
}
所以如果想要在Activity
的onResume
生命周期中执行某个操作,可以直接调用:
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
时取消协程
添加最新的依赖:
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-rc03"
该依赖添加了顶级函数liveData()
,返回CoroutineLiveData
对象,源码如下:
@UseExperimental(ExperimentalTypeInference::class)
fun liveData(
context: CoroutineContext = EmptyCoroutineContext,
timeoutInMs: Long = DEFAULT_TIMEOUT,
@BuilderInference block: suspend LiveDataScope.() -> Unit
): LiveData = CoroutineLiveData(context, timeoutInMs, block)
internal class CoroutineLiveData(
context: CoroutineContext = EmptyCoroutineContext,
timeoutInMs: Long = DEFAULT_TIMEOUT,
block: Block
) : MediatorLiveData() {
private var blockRunner: BlockRunner?
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): 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{
override fun onChanged(t: String?) {
//observe第一个参数是LifecycleOwner,所以传this,表示该Activity,因为是LiveData,所以只会在Activity活跃时回调到onChanged方法
tv.text = it
}
})
}
}
看起来确实跟LiveData
一样,在活跃状态时才能接收到数据,那之前源码中不活跃时调用的cancel()
方法,到底是取消的什么呢?难道我们Activity
一进入不活跃状态时,就把协程给取消掉了吗?
我们再来看看blockRunner
的cancel()
方法
@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
协程,然后delay
了timeoutInMs
的时间,然后再去检查是否有活跃的观察者,如果还是没有的话,就会取消掉liveData()方法中执行的协程。所以可以看出来,在cancel
方法,并不会马上就去取消掉当前的协程,而是会推迟,如果默认的时间内还没有进入活跃状态,才会取消,这就类似于一个超时
的逻辑。timeoutInMs
参数又是在CoroutineLiveData
的构造方法里给赋了一个默认的值:
//默认超时时间为5秒
internal const val DEFAULT_TIMEOUT = 5000L
internal class CoroutineLiveData(
context: CoroutineContext = EmptyCoroutineContext,
timeoutInMs: Long = DEFAULT_TIMEOUT,
block: Block
)
所以cancel()
方法什么时候起作用呢,举个例子,上代码:
liveData {
try {
delay(3000) // 延迟3秒
emit("123")
} catch (t: Throwable) {
emit("error")
}
}.observe(this, object : Observer{
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{
override fun onChanged(t: String?) {
Log.e("result", "$t")
}
})
然后我们重复上面的操作,10秒后当Activity
变回活跃状态时发现并没有执行打印log
的逻辑。
所以就印证了我们之前看到源码中的逻辑:
blockRunner
就会取消掉协程体,Activity回到活跃状态时就拿不到想要的结果。我们在调用liveData()
方法时可以自己传入自定义的超时时间,来改变超时取消的时长。
但是在测试过程当中,发现在liveData()
的闭包中没有async
方法,查看源码时才知道,原来async
是CoroutineScope
的扩展方法,而liveData()
参数中的闭包是实现了LiveDataScope
接口的对象,而且并没有继承CoroutineScope
,所以暂时在androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-rc03
版本的liveData()
中不能执行并发的请求,不知道是Google
的bug还是另有其他的用意。所以暂时想要并发的使用协程而且搭配LiveData的话,可以这样写:
private val liveData = MutableLiveData()
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协程
有一个基本的认识,能够在项目中逐渐使用起来。