笔记

附件下载完了,为什么还是打不开?

用户点击聊天附件,应用从远端取文件,保存到设备,再把本地位置记回消息,下次打开就能复用。文件传完以后,命名、移动和数据库写回仍可能失败。

这个 Flutter App 同时支持 Android 和 iOS,还会遇到两个页面打开同一条附件、不同附件文件名相同等情况。界面上都可能表现为“下载过了,却打不开”,需要沿保存流程分清。

同一条附件点了两次,要下载两遍吗?

可以给下载入口加锁,也可以让后来的调用共用进行中的 Future。前者主要安排执行顺序,后者能让多处直接等待同一个结果。

项目按消息 ID 保存进行中的下载 Future,创建后先登记,再开始下载,结束时在 finally 中移除。后来的调用找到已有任务就返回它,不再启动第二份。

这个范围是当前服务实例、同一条消息。两条消息引用同一文件仍可能各下载一次,重启或其他进程也不共享。命中已有任务时,后一次传入的进度回调、更新回调和 force 不会追加;若两个页面都要进度,需要订阅共享状态或补充监听。

名字都叫报价单,怎样避免互相覆盖?

先检查不存在再写,会在两个任务同时执行时竞争。项目选择原始名或带编号的名字,并为约定的临时路径创建占位:

snippet
dart
await File(tmpPath).create(recursive: true, exclusive: true);

Dart File.create 的排他创建会在目标已存在时失败,让遵守同一规则的任务先占到自己的临时位置。但它没有锁住最终文件名,其他程序仍可创建最终目标,失败后的占位也需要清理。

文件先写临时位置,检查存在且非空,再尝试移动,失败时还有复制与删除临时文件的回退。这些步骤不能统称为原子提交;Dart File.rename 也可能替换已有目标,不能拿来保证不覆盖。force 分支先删旧文件后再移动,后面失败还可能失去旧版本。

消息里有本地位置,就一定能打开吗?

项目对普通文件会解析为绝对路径,检查存在且长度大于零再复用。但没有核对远端大小或内容,非空并不保证下载完整。

Android 优先保存原生导出返回的内容 URI,其他路径作回退。content:// 引用的是内容提供者管理的资源,当前分支直接复用引用,没有实际打开检查,权限过期或资源消失要到读取时才能发现。

iOS 放在应用 Documents 下,消息保存相对路径,访问时再拼当前目录。因此读取时要区分相对路径、绝对路径和内容 URI,不能全部直接交给 File。它们也只对当前设备有意义,其他设备仍需远端文件引用。

文件已经落地,消息为什么还记着旧路径?

这类流程可以把下载、移动与消息写回的结果分别记录,失败时才知道应该恢复哪一步,同时检查残留文件与平台引用的可读性。

文件与数据库是两份状态。项目取得最终位置后先修改消息对象、通知界面,再写数据库。数据库抛错时,内部方法捕获并返回 null,但已经保存的文件不会随之撤销。

于是可能出现 UI 曾收到新位置、数据库仍是旧值的情况。重新打开时,先对照实际文件与消息引用,才能区分文件未落地和写回未完成。

返回上一页