使用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
相关产品推荐
相关产品推荐

