You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用this.trySend()时作用域异常导致Flow无法触发collect的问题

解决Firebase Storage上传后通过Flow返回下载链接的问题

你遇到的问题核心是Firebase任务回调的线程环境与callbackFlow的协程上下文不兼容,同时未正确处理通道的发送失败和关闭逻辑,导致回调内的trySend无法被collect捕获。具体来说:

  • Firebase的addOnCompleteListener回调运行在非协程线程(如Android主线程或Firebase后台线程),虽然trySend是线程安全的,但如果通道未准备好接收(或发送失败未处理),数据会直接丢失。
  • 原代码未在发送完成后关闭通道,也未处理trySend的失败情况,同时未在awaitClose中移除监听器,可能导致内存泄漏或协程挂起不释放。

解决方案

有两种可靠的实现方式,推荐第二种(逻辑更简洁,适合单次结果返回的场景):

方案一:修复callbackFlow的实现

调整callbackFlow的通道管理逻辑,确保回调内的发送操作正确,并在适当时机关闭通道、移除监听器:

fun uploadImage(eventId: String, imageUri: String): Flow<String> = callbackFlow {
    val uri = imageUri.toUri()
    val storageRef = storage.reference
    val imageRef = storageRef.child(eventId).child("large.jpg")
    val uploadTask = imageRef.putFile(uri)

    // 保存当前通道引用
    val channel = this

    // 定义上传完成监听器
    val completeListener = OnCompleteListener<Uri> { task ->
        if (task.isSuccessful) {
            val downloadUrl = task.result.toString()
            // 尝试发送结果,失败则关闭通道并传递异常
            channel.trySend(downloadUrl).onFailure {
                channel.close(it)
            }
            // 单次结果返回后关闭通道
            channel.close()
        } else {
            val error = task.exception ?: RuntimeException("图片上传失败")
            channel.close(error)
        }
    }

    // 绑定Firebase任务与监听器
    uploadTask.continueWithTask { task ->
        if (!task.isSuccessful) {
            task.exception?.let { throw it }
        }
        imageRef.downloadUrl
    }.addOnCompleteListener(completeListener)

    // 协程取消时清理资源
    awaitClose {
        uploadTask.removeOnCompleteListener(completeListener)
        uploadTask.cancel()
    }
}

方案二:用flow + suspendCancellableCoroutine实现(推荐)

由于上传操作是单次结果返回,用suspendCancellableCoroutine封装Firebase异步任务,再通过flow发射结果,逻辑更简洁直观:

fun uploadImage(eventId: String, imageUri: String): Flow<String> = flow {
    val downloadUri = suspendCancellableCoroutine<Uri> { continuation ->
        val uri = imageUri.toUri()
        val storageRef = storage.reference
        val imageRef = storageRef.child(eventId).child("large.jpg")
        val uploadTask = imageRef.putFile(uri)

        val completeListener = OnCompleteListener<Uri> { task ->
            if (task.isSuccessful) {
                // 恢复协程并返回下载Uri
                continuation.resume(task.result)
            } else {
                // 上传失败时传递异常
                val error = task.exception ?: RuntimeException("图片上传失败")
                continuation.resumeWithException(error)
            }
        }

        uploadTask.continueWithTask { task ->
            if (!task.isSuccessful) {
                task.exception?.let { throw it }
            }
            imageRef.downloadUrl
        }.addOnCompleteListener(completeListener)

        // 协程取消时终止上传任务
        continuation.invokeOnCancellation {
            uploadTask.cancel()
        }
    }
    // 发射下载链接字符串
    emit(downloadUri.toString())
}

调用代码(无需修改)

保持原有的收集逻辑即可:

class AddEvent(private val repository: Repository) {
    suspend operator fun invoke(imageUri: String) {
        val uuid = UUID.randomUUID().toString()
        repository.uploadImage(uuid, imageUri).collect { imageUrl ->
            // 处理下载链接
        }
    }
}

内容的提问来源于stack exchange,提问作者DEUS VULT

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 12:31:09