Kotlin的协程,真能提升编程效率么?

Kotlin官网:Kotlin协程是可挂起计算的实例。它在概念上类似于线程,因为它需要与其余代码同时运行的代码块。但是,协程不绑定到任何特定线程。它可以在一个线程中挂起其执行并在另一线程中恢复。协程可以被认为是轻量级线程...

Kotlin的协程刚推出时,在开发者群体中引起了不小的争论,大致有以下两个观点:

  • Kotlin的协程根本不是协程,只是个线程池框架
  • Kotlin的协程本质上还是依赖线程,没有任何性能上的优势

关于协程的更多概念及发展历程,可参考:「仓颉线程」:轻量级线程?or 协程?

本篇文章我们抛开理不清、剪不断的理论上的各种名词,回到现实,使用协程真的能提升我们的编程效率么?

消灭对称式API

什么是对称式API

在Android开发中,存在许多成对出现的API,这些API通常用于管理资源、状态、视图的显示与隐藏等。这些成对的API有助于维护应用的稳定性和性能。

这些API的正确使用对于保证资源被适时释放、避免内存泄露和保持应用的响应性至关重要。开发者在使用这些API时,应该注意它们的生命周期和上下文,确保成对的API在适当的时机被调用。

举几个例子🌰:

  • 文件、网络连接等资源的打开与关闭操作
    • InputStream.open()
    • InputStream.close()
  • 弹窗的展示与隐藏
    • dialog.show()
    • dialog.dismiss()
  • EventBus注册/解除注册
    • EventBus.getDefault().register()
    • EventBus.getDefault().unregister()
  • ...

这些成对出现的api我们姑且称之为对称式API,他们有个共同的特点,就是一旦调用其中一个方法后,一定要确保与之对应的另外一个api在合适的时机调用,否则就会产生意想不到的异常。以弹窗的展示为例:

这种依赖开发自觉调用的对称式API没有正确调用产生的问题往往非常隐蔽,难以发现,但是积累的多了后又会对业务造成不可预估的影响,还是那句话:

使用协程「消灭」对称式API

仍然以展示弹窗这样一个简单基础的场景为例,在使用协程优化代码之前,我们先来看下传统方式代码是怎样实现的:

kotlin 复制代码
class SecondActivity : AppCompatActivity() {

    private val dialog: Dialog by lazy {
        val builder = AlertDialog.Builder(this)
        builder.setTitle("title")
        builder.setMessage("这是一个弹窗")

        builder.setPositiveButton("确认") { dialog, which ->
            // ignore
        }

        builder.setNegativeButton("取消") { dialog, which ->
            // ignore
        }
        builder.create()
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)
        val mainView: View = findViewById(R.id.main)
        mainView.setOnClickListener {
            dialog.show()
        }
    }

    // 别忘了要在onDestroy里调用dialog.dismiss()
    override fun onDestroy() {
        super.onDestroy()
        if (dialog.isShowing) {
            dialog.dismiss()
        }
    }

    
}

代码很简单,重要的就是:

  • 别忘了在合适的时机正确调用dialog.dismiss()方法隐藏弹窗
  • 别忘了在合适的时机正确调用dialog.dismiss()方法隐藏弹窗
  • 别忘了在合适的时机正确调用dialog.dismiss()方法隐藏弹窗

重要的事情要说三遍。这种对称式api在使用时,就像是埋了个定时炸弹,一旦没有正确拆弹,你就只能祈祷炸弹尽量晚些爆炸了~

那换成协程式的写法会好些么?说到这有些兄弟会表示奇怪:

  • 协程和弹窗使用这类对称式api有什么关系?

利用协程结构化并发的特性,我们完全可以在写法上消灭这种对称式api:

kotlin 复制代码
fun keepDialogShowing() {
    lifecycleScope.launch {
        try {
            dialog.show()
            awaitCancellation()
        } catch (e: Exception) {
            if (dialog.isShowing) {
                dialog.dismiss()
            }
        }
    }
}

这样在Activity销毁时,lifecycleScope作用域下的协程随即被取消,弹窗也就被同步dismiss()了。

消灭回调式API

回调式API相信大家都很熟悉了,Kotlin协程的一大好处就是能够更加优雅地处理异步操作,避免回调地狱。我们不妨把格局打开,利用suspendCancellableCoroutineapi,我们能「消灭」更多Android开发中传统的回调式API。

我们仍然以弹窗为例,需求很简单,弹窗中两个按钮,确定取消,我们需要获取用户点击了哪个按钮,传统写法是这样式儿的:

kotlin 复制代码
fun showDialog(onConfirm: () -> Unit, onCancel: () -> Unit) {
    val builder = AlertDialog.Builder(this)
    builder.setTitle("title")
    builder.setMessage("这是一个弹窗")
    builder.setPositiveButton("确认") { dialog, which ->
        onConfirm()
    }

    builder.setNegativeButton("取消") { dialog, which ->
        onCancel()
    }
    val dialog = builder.create()
    dialog.show()
}

// 使用时通过回调获取用户的点击结果
showDialog({
    Toast.makeText(this, "点击了确认按钮", Toast.LENGTH_SHORT).show()
}, {
    Toast.makeText(this, "点击了取消按钮", Toast.LENGTH_SHORT).show()
})

用协程呢?

kotlin 复制代码
suspend fun getUserSelectionByDialog(): Boolean {
    return suspendCancellableCoroutine { continuation ->
        val builder = AlertDialog.Builder(this)
        builder.setTitle("title")
        builder.setMessage("这是一个弹窗")
        builder.setPositiveButton("确认") { dialog, which ->
            continuation.resumeWith(Result.success(true))
        }

        builder.setNegativeButton("取消") { dialog, which ->
            continuation.resumeWith(Result.success(false))
        }

        dialog.setOnDismissListener {
            // 别忘了处理弹窗消失的逻辑
            continuation.resumeWith(Result.success(false))
        }

        val dialog = builder.create()
        dialog.show()
        continuation.invokeOnCancellation {
            dialog.dismiss()
        }
    }
}

// 使用时同步获取用户的点击结果
lifecycleScope.launch {
    val result = getUserSelectionByDialog()
    val toastText = if (result) "点击了确认按钮" else "点击了取消按钮"
    Toast.makeText(this@SecondActivity, toastText, Toast.LENGTH_SHORT).show()
}

通过使用suspendCancellableCoroutine函数,我们将传统的回调式的api改写成了挂起函数,大大提升了代码的可读性和可维护性,调用者只需在正确的上下文中使用,就能以同步的形式获取用户的点击结果。

总结

以上我们从kotlin协程的结构化并发、替代传统回调两个场景抛砖引玉,举例说明了协程对于编程效率的提升。充分理解Kotlin 协程,了解Kotlin协程的结构化并发、异常处理等特性后,我们就可以使用协程进一步的简化异步编程、提升资源利用率和以及减少线程管理复杂性,提升我们的代码编写效率和代码质量。

我是沈剑心,我们下次见~

相关推荐
程序员码歌2 小时前
短思考第261天,浪费时间的十个低效行为,看看你中了几个?
前端·ai编程
Bigger3 小时前
Flutter 开发实战:解决华为 HarmonyOS 任务列表不显示 App 名称的终极指南
android·flutter·华为
Swift社区3 小时前
React Navigation 生命周期完整心智模型
前端·react.js·前端框架
若梦plus3 小时前
从微信公众号&小程序的SDK剖析JSBridge
前端
用泥种荷花4 小时前
Python环境安装
前端
Light604 小时前
性能提升 60%:前端性能优化终极指南
前端·性能优化·图片压缩·渲染优化·按需拆包·边缘缓存·ai 自动化
Jimmy4 小时前
年终总结 - 2025 故事集
前端·后端·程序员
烛阴4 小时前
C# 正则表达式(2):Regex 基础语法与常用 API 全解析
前端·正则表达式·c#
roman_日积跬步-终至千里4 小时前
【人工智能导论】02-搜索-高级搜索策略探索篇:从约束满足到博弈搜索
java·前端·人工智能
GIS之路4 小时前
GIS 数据转换:使用 GDAL 将 TXT 转换为 Shp 数据
前端