HarmonyOS 7+ArkTS:图像超分缓存回收预算与淘汰并发校验【鸿蒙心迹】
图像超分页面最容易把“生成成功”和“图片能够长期保留”混为一谈。原图、处理中间文件、预览图、最终导出图都可能占用应用空间。业务上刚打开的结果仍被预览组件引用,而后台的清理任务却把它识别成“很久没访问过的缓存”,两条时间线撞在一起,就会出现刚生成的图片突然消失。这篇不讨论超分模型如何推理,也不假装调用一个没有核对过的 Image Kit 模型接口。目标更窄:先在 ArkTS 里做可审计的缓存回收计划,再讨论真正删文件之前必须核查的并发条件。计划与执行分离,不靠截图里的“绿色通过”证明磁盘真的变小。
## 一、先把 304 MiB 的账算完整为演示准备一个名为 CacheQuota 的页面,批次 CACH-209。当前有六份增强图片或相关工作文件:E01=48 MiB、E02=56 MiB、E03=64 MiB、E04=52 MiB、E05=40 MiB、E06=44 MiB。六项加起来刚好 304 MiB。本例给这些文件设置一个应用自定义预算 256 MiB,因此计划需要至少腾出 48 MiB。这个预算不是 HarmonyOS 提供的统一磁盘配额,也不能代表每台设备都有 256 MiB 可用空间。真实产品仍然要考虑设备剩余空间、应用沙箱策略和编码格式差异。这里刻意全部用 MiB 计数,避免一会儿用十进制 MB、一会儿用二进制 MiB,最后让 UI 百分比和清理数量对不上。若只按文件大小倒序,64 MiB 的 E03 看起来最适合删除,但还得回答:它是否有页面引用、是否仍在任务中写入、是否属于用户保存的正式作品?缓存与用户文件必须分开建立清单,回收只能针对应用有处置权的临时产物。文件名像缓存,不意味着可以直接删除。本文固定的状态是:E01 引用数 2,E02 引用数 1,E03 引用数 0 且状态 READY,E04 正在 PROCESSING,E05 引用数 3,E06 引用数 0 且状态 READY。符合回收资格的是 E03 和 E06。对二者按上次使用顺序选择,E03 更旧,单独计划回收 64 MiB 即可,不需要继续碰 E06。## 二、回收资格比剩余空间更难计算在缓存目录里做删除计划,不应把“访问时间最早”当成唯一原则。正在生成的文件即使显示为零引用,也不能碰;被页面绑定的结果就算尺寸巨大,也应等它释放;进入相册或用户明确保存的导出文件,甚至不属于这份缓存清单。这里的 READY 只代表业务清单认为写入已完成,不代表 OS 文件句柄已经全部关闭。下面用纯 ArkTS 模型计算计划。lastUseOrder 是业务维护的递增序列,不伪装成文件系统提供的真实访问时间。排序后累计候选大小,达到超出预算的数量便停止。revision 被复制进计划,为下一节的并发复核留下证据。typescriptexport interface CacheEntry { id: string; sizeMiB: number; refCount: number; state: 'READY' | 'PROCESSING'; lastUseOrder: number;}export interface TrimPlan { revision: number; selected: string[]; reclaimMiB: number; projectedMiB: number; status: 'PLAN_READY' | 'INSUFFICIENT_RECLAIMABLE';}export function planTrim(rows: CacheEntry[], budgetMiB: number, revision: number): TrimPlan { const used: number = rows.reduce((n, row) => n + row.sizeMiB, 0); const required: number = Math.max(0, used - budgetMiB); const candidates: CacheEntry[] = rows .filter((e) => e.state === 'READY' && e.refCount === 0) .sort((a, b) => a.lastUseOrder - b.lastUseOrder); const selected: string[] = []; let freed: number = 0; for (const e of candidates) { if (freed >= required) { break; } selected.push(e.id); freed += e.sizeMiB; } return { revision, selected, reclaimMiB: freed, projectedMiB: used - freed, status: freed >= required ? 'PLAN_READY' : 'INSUFFICIENT_RECLAIMABLE' };}计划函数故意没有调用文件删除 API。它的结果只能说明“如果这些文件仍然存在、大小未变化、引用未增加且都可以成功删除,预计剩余多少”,不能保证任何字节已经被系统回收。此处还有一个需要补的输入校验:正式工程应拒绝负数大小、NaN 和重复 ID,并在生成清单时检验所属目录,防止污染后续预算。以本次人工输入计算,结果为 revision=7、selected=[E03]、reclaimMiB=64、projectedMiB=240、status=PLAN_READY。这说明预计会比预算再低 16 MiB;它不说明已经执行成功,更不代表超分画质有任何变化。## 三、为什么明明计划好了,还不能立即删做计划和用户点击“核对回收计划”之间可能发生很多事:用户重新打开 E03,后台继续写入 E04,另一个清理协程已经修改目录,甚至清单里的 64 MiB 大小已经不再准确。仅仅把旧计划中的 selected 数组交给删除函数,会把这些变化全部忽略。解决办法不是让 UI 一直转圈,而是让计划绑定目录版本。每次引用数、状态或记录集合发生影响回收资格的变化,都必须递增 revision;真正执行前重新读取当前清单,再判定版本、引用和状态。下面代码只做执行前预检,不假装这个示例已经接好了磁盘删除逻辑。typescriptexport type TrimCheck = 'ALLOW_RECHECKED' | 'STALE_PLAN' | 'BUSY_ENTRY';export function checkBeforeDelete(plan: TrimPlan, currentRevision: number, latest: CacheEntry[]): TrimCheck { if (plan.revision !== currentRevision) { return 'STALE_PLAN'; } for (const id of plan.selected) { const now: CacheEntry | undefined = latest.find((e) => e.id === id); if (!now || now.refCount !== 0 || now.state !== 'READY') { return 'BUSY_ENTRY'; } } return 'ALLOW_RECHECKED';}这里的 ALLOW_RECHECKED 只表达应用元数据复核通过。对于多进程、文件句柄和外部读写者,它还不等于删文件的原子锁。真正的实现需要把“锁定待删项、关闭相关资源、删除临时文件、核对错误返回、更新清单”放入明确的串行或事务流程;若目标文件已被引用,应该放弃而不是强删。对用户已导出的作品应采用另外的保留策略。在不同设备上,应用能够访问的目录和资源释放方式各不相同。Image Kit 可以处理图像像素数据,Core File Kit 提供文件访问相关能力,但这两类能力并不会替应用确定哪些业务资源有资格被回收。本文刻意没有写未经核验的 PixelMap 清理方法、虚构的超分推理入口或所谓“系统自动淘汰回调”。
## 四、界面只展示计划,不播放删除成功动画图片素材里的页面叫 CachePlanPage。顶部预算面板显示“304 / 256 MiB”,超出预算 48 MiB;记录区保留全部六个编号,把被保护、处理中和可候选三个状态分开。E03 用橙色边框提示“建议回收”,而 E06 虽有资格,也不会因为它同样是零引用就被顺手删除。为避免误导,操作按钮固定写“核对回收计划”,底部注明“实际删除 0”。内部 PLAN_READY 是应用标签,不是系统文件操作返回值。页面想显示预测数,可以使用下面这段 ArkUI 代码。按钮点击只切换信息展示,不执行物理删除。typescript@Entry@Componentstruct CachePlanPage { @State planReady: boolean = true; @State selectedId: string = 'E03'; @State projectedMiB: number = 240; build() { Column({ space: 12 }) { Text('CacheQuota · CACH-209').fontSize(22) Text('缓存占用 304 / 256 MiB · 超出 48 MiB') Text(`目录版本 rev=7 · 建议 ${this.selectedId} 64 MiB`) Text(this.planReady ? 'PLAN_READY' : 'RECHECK_REQUIRED') Text(`预计占用 ${this.projectedMiB} MiB · 实际删除 0`) Button('核对回收计划') .onClick(() => { this.planReady = false; }) // 重新校验前不执行删除 }.padding(20).width('100%') }}演示 IDE 的 HiLog 对应 CACH-209、rev=7、used=304、budget=256、need=48、candidate=E03、reclaim=64、projected=240。这些日志是为了说明字段如何关联而构造,未从 DevEco Studio 实际抓取。手机图展示的是核对按钮点击前的计划状态,所以仍为 PLAN_READY,没有灰度地宣称“清理完成”。
## 五、边界场景比平均性能更值得验证准备验证矩阵时,首先覆盖“缓存不足以满足计划”:例如两个无引用候选合计只有 32 MiB,而超额是 48 MiB,此时应产生 INSUFFICIENT_RECLAIMABLE,而不是勉强把仍被引用的文件拉入名单。其次是目录版本从 7 变成 8:旧计划必须返回 STALE_PLAN,重新计算,不要用一条过期计划去覆盖新页面的引用状态。还要考虑计算后实际文件已不存在、文件大小变化、删除过程中应用退到后台、磁盘满导致临时索引无法更新,以及重复执行同一计划。这些情形都应能留下脱敏的文件 ID、版本和失败原因;不要把绝对路径、用户相册目录或原始图片内容写进日志。性能方面可以分层测量清单构建、计划计算、文件删除和 UI 刷新,但不能拿本文的几个模拟数字当性能结论。算出 240 MiB 的预测占用,不意味着真实储存统计会立即变成 240;缓存文件还可能被其他对象引用,文件系统也可能延迟体现空间变化。## 六、真正值得保留的是一份可复核清单图像超分的缓存治理和画质评估是两件事。前者要解决“谁拥有文件、何时能删、删错怎么办”;后者才讨论清晰度、纹理和模型效果。把这两个任务放在同一个“优化成功”提示里,很容易制造虚假的完成感。这套应用层计划模型的价值在于:给每一份中间产物赋予稳定 ID、引用数、写入状态和目录版本;回收前先看资格,再看空间缺口,最后按版本复查。只有当真实持久化存储实现了相同合同,才能把“待核对的回收计划”推进为“已完成删除”。在此之前,让界面如实显示计划未执行,反而是更可靠的工程结果。资料核对: 华为 Image Kit:https://developer.huawei.com/consumer/cn/doc/doccenter-capabilities/image-kit ;HarmonyOS 文档中心(Core File Kit):https://developer.huawei.com/consumer/cn/doc/ ;ArkUI @State 与按钮示例:https://developer.huawei.com/consumer/cn/doc/HarmonyOS-Guides/arkts-watch 。本文没有进行 SDK 编译、真机磁盘清理或实际超分推理测试。
更多推荐




所有评论(0)