再谈协程之Callback写出协程范儿

// Get the Continuation object of the coroutine that it’s running this suspend function

suspendCoroutineUninterceptedOrReturn { uCont ->

// Take over the control of the coroutine. The Continuation’s been

// intercepted and it follows the CancellableContinuationImpl lifecycle now

val cancellable = CancellableContinuationImpl(uCont.intercepted(), …)

/* … */

// Call block of code with the cancellable continuation

block(cancellable)

// Either suspend the coroutine and wait for the Continuation to be resumed

// manually in block or return a result if block has finished executing

cancellable.getResult()

}

Streaming data

如果我们想获取多个数据流(使用NetAPI.getDataList函数),我们就需要使用Flow创建一个数据流。理想的API应该是这样的。

fun NetAPI.getDataListFlow(): Flow

要将基于回调的流媒体API转换为Flow,我们需要使用创建Flow的callbackFlow构建器。在callbackFlow lambda中,我们处于Coroutine的上下文中,因此,可以调用suspend函数。与flow构建器不同,callbackFlow允许通过send函数从不同CoroutineContext发出值,或者通过offer函数在协程外发出值。

通常情况下,使用callbackFlow的流适配器遵循这三个通用步骤。

  • 创建回调,使用offer将元素添加到流中。

  • 注册该回调。

  • 等待消费者取消循环程序并取消对回调的注册。

示例代码如下所示。

// 向consumer发送Data updates

fun NetAPI.getDataListFlow() = callbackFlow {

// 当前会在一个协程作用域中创建一个新的Flow

// 1. 创建回调,使用offer将元素添加到流中

val callback = object : NetCallback() {

override fun success(result: Result?) {

result ?: return // Ignore null responses

for (data in result.datas) {

try {

offer(data) // 将元素添加至flow

} catch (t: Throwable) {

// 异常处理

}

}

}

}

// 2. 注册该回调,从而获取数据流

requestDataUpdates(callback).addOnFailureListener { e ->

close(e) // 异常时close

}

// 3. 等待消费者取消循环程序并取消对回调的注册,这样会suspend当前协程,直到这个flow被关闭

awaitClose {

// 移除监听

removeLocationUpdates(callback)

}

}

callbackFlow背后的原理

在协程内部,callbackFlow会使用channel,它在概念上与阻塞队列非常相似。channel都有容量配置,限定了可缓冲元素数的上限。

在callbackFlow中所创建channel的默认容量为64个元素,当你尝试向已经满的channel添加新元素时,send函数会将数据提供方挂起,直到新元素有空间能加入channel为止,而offer不会将相关元素添加到channel中,并会立即返回false。

awaitClose背后的原理

awaitClose的实现原理其实和suspendCancellableCoroutine是一样的,参考下下面的代码中的注释。

public suspend fun ProducerScope<*>.awaitClose(block: () -> Unit = {}) {

try {

// Suspend the coroutine with a cancellable continuation

suspendCancellableCoroutine { cont ->

// Suspend forever and resume the coroutine successfully only

// when the Flow/Channel is closed

invokeOnClose { cont.resume(Unit) }

}

} finally {

// Always execute caller’s clean up code

block()

}

}

有啥用?


将基于回调的API转换为数据流,这玩意儿到底有什么用呢?我们拿最常用的View.setOnClickListener来看下,它既可以看作是一个One-shot的场景,也可以看作是数据流的场景。

我们先把它改写成suspendCancellableCoroutine形式,代码如下所示。

suspend fun View.awaitClick(block: () -> Unit): View = suspendCancellableCoroutine { continuation ->

setOnClickListener { view ->

if (view == null) {

continuation.resumeWithException(Exception(“error”))

} else {

block()

continuation.resume(view)

}

}

}

使用:

lifecycleScope.launch {

binding.test.awaitClick {

Toast.makeText(this@MainActivity, “loading”, Toast.LENGTH_LONG).show()

}

}

嗯,有点一言难尽的感觉,就差脱裤子放屁了。我们再把它改成数据流的场景。

fun View.clickFlow(): Flow {

return callbackFlow {

setOnClickListener {

trySend(it) // offer函数被Deprecated了,使用trySend替代

}

awaitClose { setOnClickListener(null) }

}

}

使用:

lifecycleScope.launch {

binding.test.clickFlow().collect {

Toast.makeText(this@MainActivity, “loading”, Toast.LENGTH_LONG).show()

}

}

好了,屁是完全放出来了。

可以发现,这种场景下,强行硬套这种模式,其实并没有什么卵用,反而会让别人觉得你是个智障。

那么到底什么场景需要使用呢?我们可以想想,为什么需要Callbback。

大部分Callback hell的场景,都是异步请求,也就是带阻塞的那种,或者就是数据流式的数据产出,所以这种仅仅是调用个闭包的回调,其实不能叫回调,它只是一个lambda,所以,我们再来看一个例子。

现在有一个TextView,显示来自一个Edittext的输入内容。这样一个场景就是一个明确的数据流场景,主要是利用Edittext的TextWatcher中的afterTextChanged回调,我们将它改写成Flow形式,代码如下所示。

fun EditText.afterTextChangedFlow(): Flow<Editable?> {

return callbackFlow {

val watcher = object : TextWatcher {

override fun afterTextChanged(s: Editable?) {

trySend(s)

}

override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}

override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}

}

addTextChangedListener(watcher)

awaitClose { removeTextChangedListener(watcher) }

}

}

使用:

lifecycleScope.launch {

with(binding) {

test.afterTextChangedFlow().collect { show.text = it }

}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

下图是我进阶学习所积累的历年腾讯、头条、阿里、美团、字节跳动等公司2019-2021年的高频面试题,博主还把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节

整理不易,望各位看官老爷点个关注转发,谢谢!祝大家都能得到自己心仪工作。

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

下图是我进阶学习所积累的历年腾讯、头条、阿里、美团、字节跳动等公司2019-2021年的高频面试题,博主还把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节

[外链图片转存中…(img-oNDkdAM7-1712378724274)]

整理不易,望各位看官老爷点个关注转发,谢谢!祝大家都能得到自己心仪工作。

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
  • 9
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值