0%

Kotlin-回调如何转成挂起函数

Kotlin-回调如何转成挂起函数

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第 9 天,点击查看活动详情


前言

笔者在项目中开始使用 Kotlin 时还是 Java 和 Kotlin 混合的项目,所以对于一些 Java 库提供的回调接口,再使用 Java 的方式调用感觉使用了假的 Kotlin。

本文记录下笔者在使用 Kotlin 过程中,把 Java 回调转成挂起函数的过程,不止是 Java 回调,Kotlin 回调也是可以的。

单一回调方法

单一回调方法是最容易转成挂起函数的,以 Android 中常见的点击事件接口为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 自定义的View,代表Android中的View
data class View(val viewId: Int)

// 自定义的OnClickListener,代表Android中的OnClickListener
fun interface OnClickListener {

fun onClick(view: View)
}

// 自定义的setOnClickListener,代表Android View中的setOnClickListener
fun setOnClickListener(listener: OnClickListener) {
// 启动一个线程
thread {
// 睡眠2秒
Thread.sleep(2000)

// 调用回调方法
listener.onClick(View(-1))
}
}

以上代码是对 Android 中点击事件的抽象,正常情况下,我们应该是以下面的形式调用 setOnClickListener 方法:

1
2
3
4
5
6
fun main() {

setOnClickListener { view ->
view.viewId
}
}

那我们怎么把 setOnClickListener 方法转成挂起函数呢?就是使用 Kotlin 提供的 suspendCoroutine 方法,suspendCoroutine 方法可以帮我们拿到当前协程的 Continuation 实例,笔者在 Kotlin-挂起函数挂起了啥? 中简单介绍了 suspendCoroutine 的实现。

下面让我们把回调转成挂起函数:

1
2
3
4
5
6
7
suspend fun setOnClickListenerSuspend(): View {
return suspendCoroutine { continuation ->
setOnClickListener { view ->
continuation.resume(view)
}
}
}

首先声明挂起方法 setOnClickListenerSuspend 返回值为回调方法中的入参类型 View,接着写挂起函数的方法体,直接调用 suspendCoroutine 返回,在 suspendCoroutine 的 block 中调用原来的 setOnClickListener 传入回调,最后在回调中调用 Continuation#resume 函数传入回调方法的View 类型的入参。

这样就完成了单一回调方法转成挂起函数,我们调用下试试,与原来回调的方式对比一下:

1
2
3
4
5
6
7
8
9
10
11
12
// main方法增加suspend关键字
suspend fun main() {

// 回调的方式
setOnClickListener { view ->
view.viewId
}

// 挂起函数的方式
val view = setOnClickListenerSuspend()
view.viewId
}

我们通过 suspendCoroutine 把单一回调方法转成了挂起函数,与原来回调的方式相比,代码顺序上是挂起函数是同步调用的形式,看起来更直观一些。

两个回调方法

两个回调方法我们常见的是 OkHttp 中的网络请求回调:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
data class Response(val value: String)

interface Callback {

fun onFailure(e: IOException)

@Throws(IOException::class)
fun onResponse(response: Response)
}

fun sendRequest(callback: Callback) {
thread {
Thread.sleep(2000)
try {
callback.onResponse(Response("gudongAndroid"))
} catch (e: IOException) {
callback.onFailure(e)
}
}
}

正常情况下我们调用应该是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
fun main() {

sendRequest(object : Callback {
override fun onFailure(e: IOException) {
println(e)
}

override fun onResponse(response: Response) {
println(response.value)
}
})
}

通过 object 关键字构造回调的匿名类然后传入 sendRequest 方法,现在我们仍然可以使用 suspendCoroutine 方法把 sendRequest 方法转成挂起函数:

1
2
3
4
5
6
7
8
9
10
11
suspend fun sendRequestSuspend() = suspendCoroutine<Response> { continuation ->
sendRequest(object : Callback {
override fun onFailure(e: IOException) {
continuation.resumeWithException(e)
}

override fun onResponse(response: Response) {
continuation.resume(response)
}
})
}

onFailure 方法中调用 Continuation#resumeWithException 函数抛出异常,在 onResponse 方法中还是调用 Continuation#resume 函数传入结果,我们对比下前后使用上的差异:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
suspend fun main() {

// 回调的方式
sendRequest(object : Callback {
override fun onFailure(e: IOException) {
println(e)
}

override fun onResponse(response: Response) {
println(response.value)
}
})

// 挂起函数的方式
try {
val response = sendRequestSuspend()
println(response.value)
} catch (e: IOException) {
println(e)
}
}

使用挂起函数的方式,Continuation#resumeWithException 函数抛出的异常一般使用 try catch 捕获异常,调用方可以根据异常类型进行不同的处理逻辑。

多个回调方法

总结

欢迎关注我的其它发布渠道