Android Kotlin开发环境下,协程是我们绕不开的话题。其以同步或者线性的思路实现复杂的异步或者并发逻辑所表现出来的优势,是Retrofit、OkHttp、RxJava等无法比拟的。
那么Kotlin的协程究竟有哪些创建方式呢?下面我们来慢慢扒一扒。
Android Kotlin的协程创建,一共有7中方式。
1、runBlocking
runBlocking创建一个协程的作用域,它可以保证在协程作用域内的所有代码和子协程没有全部执行完之前一直阻塞当前线程。需要注意的是:runBlocking只在测试环境下使用,正式环境中使用容易产生一些性能上的问题。
// 显式使用 runBlocking 协程构建器来阻塞fun test() {runBlocking {// 开始执行主协程GlobalScope.launch { // 在后台启动一个新的协程并继续delay(1000L)Log.e(TAG, "World!")}Log.e(TAG, "Hello,") // 主协程在这里会立即执行delay(1000L) // 延迟 1 秒来保证 JVM 存活repeat(3) {Log.e(TAG, "协程执行$it 线程id:${Thread.currentThread().id}")delay(4500)}runBlocking { // this: CoroutineScopelaunch { // 在 runBlocking 作用域中启动一个新协程delay(1000L)Log.e(TAG, "World!")}Log.e(TAG, "Hello,")}}}// 这里的 runBlocking { …… } 作为用来启动顶层主协程的适配器。// 然后在方法体内部 使用任何喜欢的断言风格来使用挂起函数或者完成我们预定的业务
2、GlobalScope.launch 和 GlobalScope.async
每次都创建一个顶层,也就是全局性的协程,因此在Android开发中,一般不建议直接通过他来创建协程。这种协程当应用程序运行结束时也会跟着一起结束。
fun launch() {Log.e(TAG, "主线程id:${mainLooper.thread.id}" + "\t主线程name:" + Thread.currentThread().name)GlobalScope.launch { // 启动一个新协程并保持对这个作业的引用delay(1000L)Log.e(TAG, "World!")suspendingPrint()}GlobalScope.launch { // 在后台启动一个新的协程并继续delay(1000L) // 非阻塞的等待 1 秒钟(默认时间单位是毫秒)Log.e(TAG, "TAGTAG:World!") // 在延迟后打印输出Log.e(TAG, "协程执行结束 -- 线程id:${Thread.currentThread().id}")}}
GlobalScope.launch创建协程时,还可以实现线程切换:
// 后台执行GlobalScope.launch(Dispatchers.IO){ // 👈 在 IO 线程执行任务saveDataToDataBase(data)}// 前台执行GlobalScope.launch(Dispatchers.Main){ // 👈 在 UI 线程执行任务val image = spendGetImage(imageId)avatarIv.setImageBitmap(image) // 👈 执行结束后,自动切换回 UI 线程}GlobalScope.launch(Dispatchers.Main) {//👇 async 函数启动新的协程val avatar = async {api.getAvatar(user)} // 获取用户头像val logo = async {api.getCompanyLogo(user)} // 获取用户所在公司的 logo//👇 获取返回值show(this@ArticleActivity, avatar.await().toString(), logo.await().toString())// 更新 UI}
GlobalScope.async() {// TODO:}GlobalScope.async(Dispatchers.Main) {// TODO:}
3、CoroutineScope.launch
1、CoroutineScope函数也是一个挂起函数,因此可以在任何其他挂起函数中调用。它的特点是会继承外部的协程的作用域并创建一个子协程,借助这个特性,我们就可以给任意挂起函数提供协程作用域。2、CoroutineScope函数和runBlocking函数还有点类似,它可以保证其作用域内的所有代码和子协程在全部执行完之前,外部的协程会一直被挂起。 但是: CoroutineScope函数只会阻塞当前协程,既不影响其他协程,也不影响任何线程,因此是不会造成任何性能上的问题的。 而runBlocking函数由于会挂起外部线程,如果你恰好又在主线程中调用它的话,那么就有可能会导致界面卡死的情况,所以不太推荐在实际项目中使用。需要注意的是,CoroutineScope必须配合着 Dispatchers线程切换使用。
CoroutineScope(Dispatchers.IO).launch {var list:List<String>val pathName = activity.externalCacheDirspathName.forEach loop@{if (it.absolutePath.endsWith("documents")) {val mBufferedReader = File(it.absolutePath).inputStream().reader(Charsets.UTF_8).buffered()mBufferedReader.use {list = it.readLines()println("documents的内容是:\n${list.joinToString()}")}println("use 函数的 mBufferedReader:$mBufferedReader")list.forEach {println("每一行的内容是:$it")}return@loop}}}CoroutineScope(Dispatchers.Main).launch {// TODO:}
4、MainScope().launch
默认在主线程开启一个协程
MainScope().launch(Dispatchers.Main) {println("CurrentThead Main name: ${Thread.currentThread().name}")}MainScope().launch(Dispatchers.IO) {println("CurrentThead IO name: ${Thread.currentThread().name}")}MainScope().launch(Dispatchers.Default) {println("CurrentThead Default name: ${Thread.currentThread().name}")}MainScope().launch {println("CurrentThead name(No Dispatchers): ${Thread.currentThread().name}")}
运行结果如下:
5、viewModelScope.launch
在MVVM开发模式中,我们还可以通过 viewModelScope.launch 发起一个协程。其实viewModelScope是CoroutineScope的子类。优势在于viewModelScope 是可以感知ViewModel的生命周期。
class ArticleViewModel : ViewModel() {fun createData() {viewModelScope.launch {println("ViewModel CurrentThead No Dispatchers name: ${Thread.currentThread().name}")}viewModelScope.launch(Dispatchers.IO) {println("ViewModel CurrentThead IO name: ${Thread.currentThread().name}")}viewModelScope.launch(Dispatchers.Main) {println("ViewModel CurrentThead Main name: ${Thread.currentThread().name}")}viewModelScope.launch(Dispatchers.Default) {println("ViewModel CurrentThead Default name: ${Thread.currentThread().name}")}}
}
6、lifecycleScope.launchxxx
最后,我们还可以通过 lifecycleScope 来创建协程。有意思的是,在老的协程版本中, 我们还可以通过 lifecycleScope 启动指定生命周期的协程:
lifecycleScope.launchWhenCreated {// TODO:}lifecycleScope.launchWhenStarted {// TODO:}lifecycleScope.launchWhenResumed {// TODO:}
但目前来说,这种创建协程的方式已经被google放弃了。我们只能通过 lifecycleScope 创建如下协程:
lifecycleScope.launch(Dispatchers.xxx) {// TODO:}
因为是调用lauch()方法创建,因此,我们可以在lauch()中指定运行协程的线程。
7、async与await
1、async是一个子协程,必须在协程作用域当中才能调用,它会创建一个新的子协程并返回一个Deferred对象,如果我们想要获取async函数代码块的执行结果,只需要调用Deferred对象的await()方法即可。
2、调用了async函数之后,代码块中的代码就会立刻开始执行。当调用await()方法时,如果代码块中的代码还没执行完,那么await()方法会将当前协程阻塞住,直到可以获得async函数的执行结果调用了async函数之后,代码块中的代码就会立刻开始执行。当调用await()方法时,如果代码块中的代码还没执行完,那么await()方法会将当前协程阻塞住,直到可以获得async函数的执行结果。
GlobalScope.launch(Dispatchers.Main) {//👇 async 函数启动新的协程val avatar = async {//api.getAvatar(user)} // 获取用户头像val logo = async {//api.getCompanyLogo(user)} // 获取用户所在公司的 logo//👇 获取返回值val avatarAwait = avatar.await().toString()val logoAwait = logo.await().toString()show(activity, avatarAwait , logoAwait )// 更新 UI}
8、withContext
与async一样,withContext也是一个子协程,必须运行在协程作用域或者suspend修饰的挂起函数中。可以实现线程的切换。在Android中,最典型的应用场景莫过于在子线程中去处理耗时操作,然后切到主线程中来更新UI
suspend fun spendGetImage(imageId:String){withContext(Dispatchers.IO){getImage(imageId)}}suspend fun suspendingGetImage(imageId: String) = withContext(Dispatchers.IO) {getImage(imageId)}
上面我们介绍了8中创建协程的方式。其中:
runBlocking创建的协程,只能在测试环境下使用,否则会影响性能。
GlobalScope、CoroutineScope、MainScope、viewModelScope、lifecycleScope创建的协程,其作用域都是顶级作用域。
async与await、withContext创建的作用域都是子域,他必须依附于顶级作用域运行。