ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

HarmonyOS 7 Core File Kit + DocumentViewPicker:批量文档导入中的 URI 去重、并发复制限流与文件句柄闭环【鸿蒙心迹】

HarmonyOS 7 Core File Kit + DocumentViewPicker:批量文档导入中的 URI 去重、并发复制限流与文件句柄闭环【鸿蒙心迹】 DocumentImportLab通过DocumentViewPicker一次选择多份 PDF再复制进应用沙箱。单文件时没问题选二十多份后会遇到重复 URI、I/O 峰值和 FD 泄漏。本文只处理三件事URI 去重、复制并发限流、源/目标文件描述符在所有路径上闭环。本次固定数据Sessiondoc_import_20261001_20StateIMPORTEDSelected24Unique URIs21Duplicates3Concurrent Limit4Copied21Failed0Open FDs0Total Size86.4 MBOutput Dirimports/20261001_20Last Filespec_21.pdfLast Cost842 ms一、DocumentViewPicker 只负责让用户选文件导入策略仍然属于业务Core File Kit 的DocumentViewPicker会打开系统文件选择页应用不需要为了 Picker 本身额外申请文件选择权限但它必须在 UIAbility 场景中调用。我先把 Picker 这一层保持得很薄import { picker } from kit.CoreFileKit private async pickDocuments(): Promisestring[] { const context getContext(this) as common.UIAbilityContext const options new picker.DocumentSelectOptions() const documentPicker new picker.DocumentViewPicker(context) return await documentPicker.select(options) }Picker 返回 URI 数组。URI 不是普通本地路径后续打开、读取和复制都应该在明确生命周期内完成。二、第一层治理是 URI 去重别把重复文件交给后面的 I/O本次用户选择了 24 个 URI其中有 3 个重复项。最简单也最可靠的做法就是先用 Set 去重private normalizeUris( selected: string[] ): string[] { const unique Array.from(new Set(selected)) this.selectedCount selected.length this.uniqueCount unique.length this.duplicates selected.length - unique.length return unique }最终得到Selected24、Unique URIs21、Duplicates3。去重使用完整 URI而不是文件名不同目录出现同名文件很正常。三、21 个文件不要一次性 Promise.all复制并发我固定为 4批量复制最容易写成await Promise.all(uris.map(copyOne))文件少时没问题用户一次选很多大 PDF 后就会同时打开一批源文件和目标文件FD 与磁盘 I/O 峰值都不好控制。这次我直接按 4 个一组执行private async copyInBatches( uris: string[] ): Promisevoid { const LIMIT 4 for (let i 0; i uris.length; i LIMIT) { const group uris.slice(i, i LIMIT) await Promise.all( group.map((uri, offset) this.copyOne( uri, i offset 1 ) ) ) } }并发限制的目标不是追求最短耗时而是控制 I/O 峰值。本次Concurrent Limit421 个文件全部成功Last Cost842 ms。大文件场景应进一步转到异步 I/O 或工作线程。四、真正决定稳定性的是 finally 里把两个 FD 都关掉单个 URI 的复制看起来很简单打开源 URI、创建沙箱文件、copy、close。问题通常出在异常路径。源文件打开成功目标文件创建失败或者 copy 到一半抛异常如果 close 只写在正常路径FD 就会泄漏。所以我把资源闭环写死在finallyimport { fileIo } from kit.CoreFileKit private copyOne( uri: string, index: number ): void { let src: fileIo.File | undefined let dst: fileIo.File | undefined const name spec_${index.toString().padStart(2, 0)}.pdf const outPath ${this.outputDir}/${name} try { src fileIo.openSync( uri, fileIo.OpenMode.READ_ONLY ) dst fileIo.openSync( outPath, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE ) fileIo.copyFileSync( src.fd, dst.fd ) this.copied this.lastFile name } catch (_) { this.failed } finally { if (src) { fileIo.closeSync(src) } if (dst) { fileIo.closeSync(dst) } } }最终Open FDs0。只有异常路径也能保证 close批量导入才算闭环。五、输出目录和文件名最好由业务自己管理我没有直接把用户原文件名当成沙箱文件名而是写到imports/20261001_20/spec_01.pdf一直到spec_21.pdf这样既避免原文件名冲突也方便按批次清理。若必须保留原名就把“展示名”和“沙箱存储名”分开保存。六、失败不能让整批任务失去可解释性正式项目会遇到 URI 打不开、空间不足等失败。我会让单文件失败只记录错误批次继续如果要求“全成或全败”则需要临时目录和最终 commit。七、调试页只保留跟资源闭环有关的数据工程拆成 DocumentImportPage、UriDeduper、CopyLimiter 和 ImportFileCopier。HiLog 固定输出picker selected24dedupe unique21 duplicates3copy concurrency4copied21 failed0openFDs0lastFilespec_21.pdfState: COPYING - IMPORTED八、最终运行结果里Open FDs0 比 Copied21 更重要运行页最终显示Selected24Unique URIs21Duplicates3Concurrent Limit4Copied21Failed0Open FDs0Total Size86.4 MBOutput Dirimports/20261001_20Last Filespec_21.pdfLast Cost842 ms如果Open FDs还在增加这次导入就不能算完成。九、正式项目还要处理几个 Picker 边界正式项目还要注意Picker 应从合适的 UIAbility 上下文拉起URI 不应被当永久本地路径系统可选择数量不等于应用应同时处理数量用户取消后停止继续提交 copy但已打开的 FD 仍必须走 finally。十、这次真正补上的是“选择文件”和“安全导入”之间那一段最终状态链路是PICKED → DEDUPED → COPYING → IMPORTEDDocumentViewPicker 负责让用户安全选择文件Set 负责去重并发分组控制 I/O 峰值finally 负责把每个文件句柄关干净。批量导入真正稳定是每一步都有数量边界、资源边界和失败边界。
返回列表