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协程的一大好处就是能够更加优雅地处理异步操作,避免回调地狱。我们不妨把格局打开,利用suspendCancellableCoroutine
api,我们能「消灭」更多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协程的结构化并发、异常处理等特性后,我们就可以使用协程进一步的简化异步编程、提升资源利用率和以及减少线程管理复杂性,提升我们的代码编写效率和代码质量。
我是沈剑心,我们下次见~