为什么要使用协程
举几个开发中常见的例子
- 从服务器拉取一张图片,下载,裁剪后展示在Activity上?
- 接问题1, 期间Activity关闭了怎么办?
- 接问题1, 如果是多张图片怎么同时展示在Activity上?
问题1 我先用Java描述下
// Bolts Task写法
public fun demoMothed1ForBolts() {
Task.callInBackground(Callable {
// 1. 获取图片URL
return@Callable getUrl()
}).continueWith(Continuation {
// 2. 获取图片
val connection = URL(it.result).openConnection() as HttpURLConnection
return@Continuation if (connection.responseCode == 200) BitmapFactory.decodeStream(connection.inputStream) else null
}, Task.BACKGROUND_EXECUTOR).continueWith(Continuation {
// 3. 裁剪图片
it.result?.let {
return@Continuation Bitmap.createBitmap(it, it.width / 2, it.height / 2, it.width / 2, it.height / 2)
}
return@Continuation null
}, Task.BACKGROUND_EXECUTOR).continueWith(Continuation {
// 4. 展示图片
it.result?.let {
findViewById(R.id.imageView1).setImageBitmap(it)
}
null
}, Task.UI_THREAD_EXECUTOR)
}
再用kotlin+协程描述下
public fun demoMothed1ForCoroutine() = coroutineScope.launch { val bitmap = async(Dispatchers.IO) { createTargetBitmap() } findViewById (R.id.imageView1).setImageBitmap(bitmap.await()) } private fun createTargetBitmap(): Bitmap? {
getUrl()?.let {
val connection = URL(it).openConnection() as HttpURLConnection
val sourceBitmap = if (connection.responseCode == 200) BitmapFactory.decodeStream(connection.inputStream) else null
sourceBitmap?.let {
return Bitmap.createBitmap(it, it.width / 2, it.height / 2, it.width / 2, it.height / 2)
}
return null;
}
}
因为依赖了Facebook的开源的第三方线程库Bolts,感觉Java的代码调理还算清晰,和使用协程看起来差异不大.
但是如果不使用这个线程库或者使用系统提供的AsyncTask,这里面会涉及到3次异步回调,代码可读性可能会大大降低. 也就是Java代码要达到协程的条理性必须依赖一些好的线程框架.
问题2
我仔细看了下Bolts的API,没有发现有方法让我可以取消正在运行的Task.
但是协程因为有域的概念,取消协程域,那么运行在这个域中的所有任务都会被取消了,从而可以防止在Activity被Destory后Task还在运行.
override fun onDestroy() {
super.onDestroy()
coroutineScope.cancel()
}
问题3
listOf(async { createTargetBitmap(url1) }, async { createTargetBitmap(url2) }).awaitAll();
同样如果是多个任务同时进行的话,使用线程回调不太好处理.
回调回来的时候我需要去检查其他线程里面的资源是否准备好,然后再去刷新.
协程怎么用
结合上面的栗子,可以看出协程是很好上手的,看看协程里面的关键字
1. suspend
挂起.
只能修饰方法 suspend fun
表示该方法只能在协程里面调用
表示该方法方法体内可以使用其他suspend方法
注意 这个关键字只是告诉编译器这是一个挂起函数,并没有实际执行.
2. CoroutineScope
协程作用域.
一般使用launch或者async创建的协程任务或者叫子域,都是运行在域里面,而域的作用其实就是帮助我们管理这些子域.
Android推荐使用CoroutineScope的方式运行协程,这样当外部组件生命周期结束的时候我们可以取消掉正在执行的任务.
override fun onDestroy() { super.onDestroy() coroutineScope.cancel() }
使用CoroutineScope运行,如果其中的一个子域抛出异常会导致整个协程作用域结束执行.
3.CoroutineContext/Dispatchers
协程上下文
协程作用域,子域构造函数里面都需要一个上下文,用来指名协程具体运行的线程
public fun CoroutineScope(context: CoroutineContext): CoroutineScope = ContextScope(if (context[Job] != null) context else context + Job())
而Dispatchers和CoroutineContext的关系就像是 线程和线程池的关系(ExecutorService/Executors),类似于一个静态工厂.
Type 作用
Dispatchers.Main 主线程
Dispatchers.IO IO子线程
Dispatchers.Default
Dispatchers.Unconfined CPU密集类型子线程
4. CoroutineStart
启动模式
这个属性用的较少,一般都是使用默认模式CoroutineStart.DEFAULT. 使用CoroutineStart.LAZY 这个属性可以让定义和启动分开,类似于Java里面的
Thread t = new Thread(){...};t.start()
使用Kotlin
val one = async(start = CoroutineStart.LAZY) { doSomethingUseful() } one.start()
5.launch/async await
子域
async和launch 的参数完全一致. 都是接受一个协程上下文,协程启动模式,闭包协程域
context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> T
他们的区别是 launch不关心返回值, async会在await的时候返回值.
如果拿不准直接使用async await组合即可,这是一个常规模式,存在多钟语言异步中.
6. withContext
子域
使用withContext 可以自动切回到调用线程的子域
coroutineScope.launch(Dispatchers.Main) { // 切换到IO线程 val image = withContext(Dispatchers.IO) { getImage(imageId) } // 执行完成这里自动回到Main线程 avatarIv.setImageBitmap(image) }
7. runBlocking
阻塞式子域
这种方式会阻塞当前线程,所以一般也不用.
协程是什么
协程(Coroutine),协程是一种并发设计模式,本质上是更轻量级的线程。在一个线程上可以同时跑多个协程。与线程相比,它更轻量、资源占用更少。
就是一个可以用简单同步代码写出安全的异步回调的线程工具库.