Kotlin版本的WanAndroid项目实战(六):Kotlin协程框架基础

Kotlin 是一⻔仅在标准库中提供最基本底层 API 以便各种其他库能够利用协程的语言。与许多其他具有类似功能的语言不同,async 与 await 在 Kotlin 中并不是关键字,甚至都不是标准库的一部分。此外,Kotlin 的 挂起函数 概念为异步操作提供了比 future 与 promise 更安全、更不易出错的抽象。

promise [ˈprɒmɪs]
n. 许诺,允诺;希望
vt. 允诺,许诺;给人以…的指望或希望
kotlinx.coroutines 是由 JetBrains 开发的功能丰富的协程库。它包含本指南中涵盖的很多启用高级协程的原语,包括 launch 、 async 等等。
本文是关于 kotlinx.coroutines 核心特性的指南,包含一系列示例,并分为不同的主题。
为了使用协程以及按照本指南中的示例演练,需要添加对 kotlinx-coroutines-core 模块的依赖,如项目中的 README 文件所述。

协程基础

本质上,协程是轻量级的线程。 它们在某些 CoroutineScope 上下文中与 launch 协程构建器 一起启动:

import kotlinx.coroutines.*

class CoroutineTest {
    fun main() {
        GlobalScope.launch {
            // 在后台启动一个新的协程并继续
            delay(1000L) // 非阻塞的等待 1 秒钟(默认时间单位是毫秒)
            println("CoroutineTest World!") // 在延迟后打印输出
        }
        println("CoroutineTest Hello,") // 协程已在等待时主线程还在继续
        Thread.sleep(8000L) // 阻塞主线程 2 秒钟来保证 JVM 存活
    }
}

桥接阻塞与非阻塞的世界

第一个示例在同一段代码中混用了 非阻塞的 delay(…) 与 阻塞的 Thread.sleep(…) 。 这容易让我们记混哪个是阻塞的、哪个是非阻塞的。 让我们显式使用 runBlocking 协程构建器来阻塞:

import kotlinx.coroutines.*
fun main() = runBlocking<Unit> { // 开始执行主协程
    GlobalScope.launch { // 在后台启动一个新的协程并继续
        delay(1000L)
        println("World!")
    }
    println("Hello,") // 主协程在这里会立即执行
    delay(2000L)
// 延迟 2 秒来保证 JVM 存活
}

结果是相似的,但是这些代码只使用了非阻塞的函数 delay。 调用了 runBlocking 的主线程会一直阻塞 直到 runBlocking 内部的协程执行完毕。
这个示例可以使用更合乎惯用法的方式重写,使用 runBlocking 来包装 main 函数的执行:

    import kotlinx.coroutines.*
    fun main() = runBlocking<Unit> { // 开始执行主协程
        GlobalScope.launch { // 在后台启动一个新的协程并继续
            delay(1000L)
            println("World!")
        }
        println("Hello,") // 主协程在这里会立即执行
        delay(2000L)   // 延迟 2 秒来保证 JVM 存活
    }

这里的 runBlocking { … } 作为用来启动顶层主协程的适配器。 我们显式指定了其返回类型 Unit ,因为在 Kotlin 中 main 函数必须返回 Unit 类型。

这也是为挂起函数编写单元测试的一种方式:

annotation class Test
class MyTest {
    @Test
    fun testMySuspendingFunction() = runBlocking<Unit> {
        // 这里我们可以使用任何喜欢的断言⻛格来使用挂起函数
    }
}

等待一个作业

延迟一段时间来等待另一个协程运行并不是一个好的选择。让我们显式(以非塞方式)等待所启动的后台 Job 执行结束:

fun main4() = runBlocking {
        val job = GlobalScope.launch { // 启动一个新协程并保持对这个作业的引用
            delay(1000L)
            println("World!")
        }
        println("Hello,")
        job.join() // 等待直到子协程执行结束
}

现在,结果仍然相同,但是主协程与后台作业的持续时间没有任何关系了。好多了。

协程的实际使用还有一些需要改进的地方。 当我们使用 GlobalScope.launch 时,我们会创建一个顶层协程。虽然它很轻量,但它运行时仍会消耗一些内存资源。如果我们忘记保持对新启动的协程的引用,它还会继续运行。如果协程中的代码挂起了会怎么样(例如,我们错误地延迟了太⻓时间),如果我们启动了太多的协程并导致内存不足会怎么样? 必须手动保持对所有已启动协程的引用并 join 之很容易出错。
有一个更好的解决办法。我们可以在代码中使用结构化并发。 我们可以在执行操作所在的指定作用域内启动协程, 而不是像通常使用线程(线程总是全局的)那样在 GlobalScope 中启动。
在我们的示例中,我们使用 runBlocking 协程构建器将 main 函数转换为协程。 包括 runBlocking在内的每个协程构建器都将 CoroutineScope 的实例添加到其代码块所在的作用域中。 我们可以在这个作用域中启动协程而无需显式 join 之,因为外部协程(示例中的 runBlocking )直到在其作用域中启动的所有协程都执行完毕后才会结束。因此,可以将我们的示例简化为:

fun main() = runBlocking { // this: CoroutineScope
    launch { // 在 runBlocking 作用域中启动一个新协程
        delay(1000L)
        println("World!")
    }
    println("Hello,")
}

作用域构建器

除了由不同的构建器提供协程作用域之外,还可以使用 coroutineScope 构建器声明自己的作用域。它会创建一个协程作用域并且在所有已启动程执行完毕子协之前不会结束。
runBlocking 与 coroutineScope 可能看起来很类似,因为它们都会等待其协程体以及所有子协程结束。 这两者的主要区别在于,runBlocking 方法会阻塞当前线程来等待, 而 coroutineScope 只是挂起,会释放底层线程用于其他用途。 由于存在这点差异,runBlocking 是常规函数,而 coroutineScope 是挂起函数。
可以通过以下示例来演示:

fun main() = runBlocking { // this: CoroutineScope
    launch {
        delay(200L)
        println("Task from runBlocking")
    }
    coroutineScope { // 创建一个协程作用域
        launch {
            delay(500L)
            println("Task from nested launch")
        }
        delay(100L)
        println("Task from coroutine scope") // 这一行会在内嵌 launch 之前输出
    }
    println("Coroutine scope is over") // 这一行在内嵌 launch 执行完毕后才输出
}

请注意,当等待内嵌 launch 时,紧挨“Task from coroutine scope”消息之后, 就会执行并输出“Task from runBlocking”
尽管 coroutineScope 尚未结束。

提取函数重构

我们来将 launch { … } 内部的代码块提取到独立的函数中。当你对这段代码执行“提取函数”重构时,你会得到一个带有 suspend 修饰符的新函数。 那是你的第一个挂起函数。在协程内部可以像普通函数一样使用挂起函数, 不过其额外特性是,同样可以使用其他挂起函数(如本例中的 delay )来挂起协程的执行。

fun main() = runBlocking {
    launch { doWorld() }
    println("Hello,")
}
// 这是你的第一个挂起函数
suspend fun doWorld() {
    delay(1000L)
    println("World!")
}

但是如果提取出的函数包含一个在当前作用域中调用的协程构建器的话,该怎么办? 在这种情况下,所提取函数上只有 suspend 修饰符是不够的。为 CoroutineScope 写一个 doWorld 扩展方法是其中一种解决方案,但这可能并非总是适用,因为它并没有使 API 更加清晰。 惯用的解决方案是要么显式将 CoroutineScope 作为包含该函数的类的一个字段, 要么当外部类实现了 CoroutineScope 时隐式取得。 作为最后的手段,可以使用CoroutineScope(coroutine Context),不过这种方法结构上不安全, 因为你不能再控制该方法执行的作用域。只有私有 API 才能使用这个构建器

协程很轻量

fun main7() = runBlocking {
        ///val i : Long =0;
        repeat(100_000) { // 启动大量的协程
            launch {
                delay(1000L)
                println("." )
            }
        }
 }

它启动了 10 万个协程,并且在一秒钟后,每个协程都输出一个点。 现在,尝试使用线程来实现。会发生什么?(很可能你的代码会产生某种内存不足的错误)

全局协程像守护线程

以下代码在 GlobalScope 中启动了一个⻓期运行的协程,该协程每秒输出“I’m sleeping”两次,之后在主函数中延迟一段时间后返回。

fun main() = runBlocking {
    GlobalScope.launch {
        repeat(1000) { i ->
            println("I'm sleeping $i ...")
            delay(500L)
        }
    }
    delay(1300L) // just quit after delay
}

你可以运行这个程序并看到它输出了以下三行后终止:

I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...

在 GlobalScope 中启动的活动协程并不会使进程保活。它们就像守护线程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一位练习时长两年半的安卓练习生根据鸿神提供的WanAndroid开放Api来制作的产品级App,基本实现了所有的功能,采用Kotlin语言,基于Material Design AndroidX MVP RxJava Retrofit等优秀的开源框架开发,注释超详细,方便大家练手如果你觉得做的还可以对你有帮助,容我卑微地向你要个star,有任何问题或建议,欢迎提交issues前言前段时间学习了 Kotlin ,就打算写一个项目来练练手刷刷熟练度,因为经常逛鸿神的WanAndroid网站,觉得很不错,Api也很丰富健全, 虽然根据WanAndroidAPi开发出的app多如牛毛,有诸多优秀的app,但是每个人的喜好与审美不一样,所以都没有做到我心目中的最完美,于是我就想做一个自己觉得最满意的app。界面的话参考了很多的app风格,根据自己的搭配实现,感觉还不错吧实现的功能首页五大模块:首页 / 项目 / 体系/ 公众号 / 我的登录注册功能导航功能搜索功能 热门搜索推荐 搜索历史记录积分功能 积分排行 积分获取记录收藏功能 收藏文章 收藏网址待办清单 添加清单 编辑清单 删除清单文章网址详情 详情中收藏 分享 浏览器打开全局修改应用主题色全局修改列表的加载动画侧滑返回 可打开与关闭文章在任何地方收藏与取消,其他界面相对应的数据也会变化布局优化,大量使用ConstraintLayout集成Bugly收集bug并实现App更新APP下载GitHub下载fir.im下载扫码应用截图    主要开源框架一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的框架-RxJava2一个依赖注入框架-Dagger2处理网络请求的框架-Okhttp一个基于OKHttp封装的网络加载框架-Retrofit转换json数据的官方框架-GsonAndroid的事件发布-Eventbus项目核心库MVP框架-MVPArms屏幕适配AndroidAutoSize一个美丽的、流体和可扩展的对话框-Material-dialogs一个强大的Fragment管理库-Fragmentation一个增强BottomNavigationView的安卓库-BottomNavigationViewEx强大、可定制、易扩展的 ViewPager 指示器框架-MagicIndicator一个强大的轮播库-BGABanner-Android一个强大并且灵活的RecyclerViewAdapter-BaseRecyclerViewAdapterHelperRecyclerView侧滑菜单,Item拖拽,滑动删除Item,自动加载更多,HeaderView,FooterView,Item分组黏贴-SwipeRecyclerView揭示效果布局-RevealLayout优雅地处理加载中,重试,无数据-Loadsir基于Android WebView的一个强大的库-AgentwebAndroid流式布局-FlowLayout基于mmap内存映射的移动端通用 key-value 组件-MMKV侧滑返回-SmartSwipe全局捕捉异常防止崩溃-CustomActivityOnCrash素材来源阿里巴巴矢量图标库借鉴了花瓣的登录页goweii项目中的App图标特别感谢感谢鸿神的WanAndroid网站提供的开放API,我在issues提了一些自己的需求,鸿神也耐心的帮忙添加了,谢谢
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值