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协程的结构化并发、异常处理等特性后,我们就可以使用协程进一步的简化异步编程、提升资源利用率和以及减少线程管理复杂性,提升我们的代码编写效率和代码质量。

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

相关推荐
飘尘几秒前
面试官:如何实现大量任务执行的调度?
前端·javascript·面试
印第安老斑鸠啊2 分钟前
微前端框架MicroApp本地开发改造篇--vite适配
前端
osspeace3 分钟前
使用husky+commitizen+semantic-release实现项目的全自动版本管理和发布
前端·javascript
Epicurus5 分钟前
使用transform: translate时出现闪烁现象如何解决
前端·css
前端卧龙人6 分钟前
别再重复造轮子,VueUse让前端开发更简单、更高效
前端
前端卧龙人6 分钟前
前端埋点与监控的核心要点
前端
前端大雄6 分钟前
前端面试之各大厂真题算法解析
前端·javascript·面试
银之夏雪丶7 分钟前
Vue 3 vs Vue 2:深入解析从性能优化到源码层面的进化
前端·vue.js
前端日常开发8 分钟前
一篇文章带你搞懂NextTick 是宏任务还是微任务
前端
EamonYang9 分钟前
实现 miniReact
前端