动图魔方 HarmonyOS 实战续篇(22):PixelMap 释放路径与连续短片段导出
一、release 路径已经实现,但不能把两轮成功写成“无泄漏”
动图魔方的抽帧与导出代码已经存在明确释放路径:探测帧释放、AVImageGenerator 释放、帧数组在 finally 中逐个释放,帧处理器创建的临时 PixelMap 也在各自作用域清理。模拟器同一进程内连续完成两次短片段导出,作品页同时回读 8 帧与 9 帧两条记录。

这组证据能证明释放代码真实参与了可运行链路,也能证明第二轮任务没有因为第一轮资源失效而立即失败;它不能证明所有内存都回落,更不能证明不存在泄漏。
二、视频生成器由外层 finally 兜底
视频抽帧器创建 AVImageGenerator 后,在最外层 finally 中调用 release()。无论中途取消、取帧失败还是调用方异常,都不会跳过生成器清理。
const generator = await media.createAVImageGenerator()
try {
generator.fdSrc = sourceFd
// 探测并逐帧获取 PixelMap
return frames
} finally {
await generator.release()
}
探测阶段使用的 PixelMap 不进入正式帧数组,读取尺寸后立即释放:
const probeFrame = await generator.fetchFrameByTime(startUs, option, {})
const probeInfo = await probeFrame.getImageInfo()
await probeFrame.release()
这两个释放点分别控制生成器和探测帧,职责不同。只释放 generator 不能替代单帧释放,只释放单帧也不能关闭媒体生成器。
三、正式帧数组在导出服务中统一释放
当前视频路径仍会一次返回 PixelMap[]。ExportService 接管数组后,用 try/finally 保证无论处理与编码是否成功,都会进入 releasePixelMaps()。
const pixelMaps = await VideoFrameExtractor.extract(
sourceUri,
duration,
fps,
signal,
options
)
try {
return await ExportService.encodePixelMaps(pixelMaps, preset, signal)
} finally {
await ExportService.releasePixelMaps(pixelMaps)
}
private static async releasePixelMaps(
pixelMaps: image.PixelMap[]
): Promise<void> {
for (const pixelMap of pixelMaps) {
try {
await pixelMap.release()
} catch (_) {
// 单帧释放失败不阻断其余帧清理
}
}
}
逐个捕获释放异常很重要。如果第一帧释放失败就直接抛出,后面的几十帧会全部滞留。
四、帧处理器也清理临时对象
除了抽出的原始帧,缩放、裁剪、字幕与颜色处理还可能创建临时 PixelMap。项目的 FrameProcessor 在复制像素或转换完成后调用 pixelMap.release(),避免把短生命周期对象交给更外层。
| 对象 | 创建位置 | 当前释放位置 |
|---|---|---|
| 探测帧 | VideoFrameExtractor | 读取尺寸后 |
| 正式视频帧 | VideoFrameExtractor | ExportService finally |
| AVImageGenerator | VideoFrameExtractor | 外层 finally |
| 图片/临时处理帧 | FrameProcessor | 对应处理作用域 |
| GIF 字节与 RGB 数组 | JS 内存 | 依赖引用清理与 GC |
最后一行是当前文章必须保留的边界:PixelMap 释放并不等于 JavaScript 大数组立即回收。RGB、索引帧和编码缓冲仍可能占用内存。
五、连续两轮导出的实际观测
第一轮使用 0.56 秒、15fps、标清,完成 8 帧、480×270、1.1MB。第二轮使用 0.61 秒、15fps、标清,完成 9 帧、480×270、1.3MB。
第二轮导出前:PID 1799,RSS 271336
第二轮授权保存后:PID 1799,RSS 275608
作品列表:视频转GIF_2(9帧)+ 视频转GIF_1(8帧)
PID 一致说明进程没有重启。RSS 增加约 4MB,没有在第二轮结束后立刻回落到更低值,因此本文不能下“内存完全回收”结论。更准确的说法是:释放路径存在,连续短任务可完成,长期稳定性仍需更多轮次和峰值采样。
六、长任务暴露的是主线程阻塞
5.1 秒、15fps、标清任务预计 75 帧。故障日志记录了抽帧进度和大量 PixelMap 创建,随后出现:
Reason: THREAD_BLOCK_6S
App main thread is not response!
因此现阶段最优先的问题不是再加一个 release(),而是改变执行模型。抽帧与 GIF 编码不能长时间占据主线程;即使最终能够释放资源,主线程先被系统判定无响应,用户仍会看到应用退桌面。
建议把改造顺序固定为:
- 重任务迁移到 Worker/TaskPool。
- 改为小批次处理,不同时持有全部 PixelMap。
- 每批完成后释放并让出主线程。
- 采集处理前、峰值、释放后和下一轮开始前四个内存点。
- 连续执行至少三轮,再讨论“稳定区间”。
七、证据边界与验收条件
当前可验收的是:短片段能连续导出;第二轮进程未重启;作品列表能回读两条;源码有 probe、generator 和正式帧释放路径。尚未验收的是:长片段稳定、取消路径稳定、坏帧后释放、三轮内存回落和后台切换。
PixelMap 生命周期相关接口可参考华为 HarmonyOS 图像开发指导。
八、总结
动图魔方已经把 PixelMap 与 AVImageGenerator 的释放职责落到了真实代码,并在同一进程完成两轮短片段导出。与此同时,75 帧任务的 THREAD_BLOCK_6S 说明释放正确不等于执行模型正确。下一阶段应把重点放在分批、异步和主线程让出,再用多轮峰值数据验证稳定性。
更多推荐



所有评论(0)