38 图片压缩与本地存储:文件管理与缓存策略
·
38 图片压缩与本地存储:文件管理与缓存策略
前言

图:38 图片压缩与本地存储:文件管理与缓存策略 运行效果截图(HarmonyOS NEXT)
拍照或从相册选取图片后,原始图片通常体积较大(单张可达 10MB+),如果不经过压缩和处理就直接存储,不仅浪费存储空间,还可能影响 App 的读写性能。
本文解析鸿蒙应用中的图片压缩策略、文件存储管理和缓存清理机制。
鸿蒙官方·文件管理文档:developer.huawei.com
项目源码仓库:harmony-app GitHub

图:图片压缩与本地存储全流程——ImagePacker 编码 → fileIo 写入 → 缓存管理
一、图片压缩策略
1.1 使用 ImagePacker 压缩
import { image } from '@kit.ImageKit'
import { fileIo as fs } from '@kit.CoreFileKit'
async function compressImage(inputPath: string, outputPath: string): Promise<void> {
// 1. 加载原图
const source = image.createImageSource(inputPath)
const pixelMap = await source.createPixelMap({
desiredPixelFormat: image.ImagePixelFormat.RGBA_8888,
desiredSize: { width: 1080, height: 1440 } // 限制最大分辨率
})
// 2. 压缩编码
const packer = image.createImagePacker()
const packedImage = await packer.packing(pixelMap, {
format: 'image/jpeg',
quality: 80 // 压缩质量 (0-100)
})
// 3. 写入文件
const file = fs.openSync(outputPath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE)
file.writeSync(packedImage.data.buffer)
fs.closeSync(file)
// 4. 释放资源
source.release()
packer.release()
}
1.2 压缩参数对比
| quality 值 | 图片质量 | 文件大小(以 1080p 为例) | 适用场景 |
|---|---|---|---|
| 90-100 | 极高 | 2-5MB | 保存到相册 |
| 80-85 | 高 | 500KB-1.5MB | AI 分析输入 |
| 60-75 | 中 | 200-600KB | 缩略图预览 |
| 30-50 | 低 | 50-200KB | 网络传输 |
1.3 压缩效果估算
// 原始照片(后置摄像头)≈ 12MP → 约 5-15MB
// 压缩到 1080p + quality=80 → 约 500KB-1.5MB
// 压缩率:约 90%
二、文件存储位置
2.1 应用目录结构
鸿蒙应用的文件存储有以下几个主要目录:
| 目录路径 | 用途 | 特点 | 存储策略 |
|---|---|---|---|
.../cache/ |
缓存目录 | 系统可随时清理 | ✅ 临时照片 |
.../files/ |
文件目录 | 持久存储 | ❌ 暂不使用 |
.../database/ |
数据库目录 | 自动管理 | RDB 存储位置 |
2.2 项目中的文件存储
// 拍照后临时保存到缓存目录
const cacheDir = `/data/storage/el2/base/cache/`
const fileName = `capture_${Date.now()}.jpg`
const filePath = `${cacheDir}${fileName}`
// 保存照片
// 在 DAO 中只存储路径
await HandwritingDao.create({
image_path: filePath, // 图片路径
// ...
})
2.3 缓存 vs 持久存储
| 对比 | 缓存 (cache) | 持久 (files) |
|---|---|---|
| 写入本项目的文件 | 拍照原始文件 | 数据库备份 |
| 清理条件 | App 设置清缓存、系统空间不足 | 手动删除 |
| 路径 | .../cache/ |
.../files/ |
| 本项目存储内容 | 临时拍摄照片 | RDB 数据库文件 |
三、文件生命周期管理
3.1 文件创建
// CapturePage — 拍照时创建
const filePath = `/data/storage/el2/base/cache/capture_${Date.now()}.jpg`
// 保存到 DB 供其他页面使用
AppStorage.setOrCreate<string>('capture_image_path', filePath)
3.2 文件使用(跨页面)
// AnalyzingPage — 读取路径
const imagePath = AppStorage.get<string>('capture_image_path') ?? ''
// 写入 DB
await HandwritingDao.create({
image_path: imagePath,
// ...
})
3.3 文件清理策略
// 清理旧缓存文件
async function cleanOldCache(maxAgeDays: number = 7): Promise<void> {
const cacheDir = `/data/storage/el2/base/cache/`
const now = Date.now()
const maxAge = maxAgeDays * 86400000
try {
// 列出缓存目录文件
const files = fs.listFileSync(cacheDir)
for (const fileName of files) {
if (fileName.startsWith('capture_')) {
const filePath = `${cacheDir}${fileName}`
const stat = fs.statSync(filePath)
if (now - stat.mtime > maxAge) {
fs.unlinkSync(filePath) // 删除旧文件
hilog.info(0x0000, 'FileManager', '清理旧缓存: %s', filePath)
}
}
}
} catch (e) {
hilog.error(0x0000, 'FileManager', '清理缓存失败: %s', JSON.stringify(e))
}
}
四、文件操作 API
4.1 项目中使用的方法
| 方法 | 用途 | 本项目使用 |
|---|---|---|
fs.openSync(path, mode) |
打开/创建文件 | ✅ 拍照保存 |
fs.closeSync(file) |
关闭文件 | ✅ 用完关闭 |
file.writeSync(buffer) |
写入二进制数据 | ✅ 图片数据 |
fs.listFileSync(dir) |
列出目录文件 | 可用于清理 |
fs.unlinkSync(path) |
删除文件 | 可用于清理 |
fs.statSync(path) |
获取文件状态 | 可用于检查 |
4.2 文件打开模式
| 模式 | 说明 |
|---|---|
fs.OpenMode.READ_ONLY |
只读 |
fs.OpenMode.WRITE_ONLY |
只写(会清空已有内容) |
fs.OpenMode.READ_WRITE |
读写 |
fs.OpenMode.CREATE |
不存在时创建 |
fs.OpenMode.APPEND |
追加写入 |
本项目使用: fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE
五、存储空间管理
5.1 存储空间估算
| 数据类型 | 单条数据大小 | 估算总量(1000 条) |
|---|---|---|
| 压缩后照片 | ~800KB | ~800MB |
| RDB 数据库 | ~5KB | ~5MB |
| App 代码 | - | ~20MB |
5.2 空间检查
import { statfs } from '@kit.CoreFileKit'
async function checkStorage(): Promise<{ free: number, total: number }> {
const stat = statfs.getFreeBytes('/data/storage/el2/base/')
const total = statfs.getTotalBytes('/data/storage/el2/base/')
return {
free: stat / (1024 * 1024), // 转换为 MB
total: total / (1024 * 1024)
}
}
// 使用示例
const space = await checkStorage()
if (space.free < 100) {
// 剩余空间不足 100MB,提示用户清理
}
六、非相机图片的存储
6.1 Demo 种子数据
DemoSeeder 生成的演示数据中,image_path 为空字符串,因为不是真实拍摄的:
await HandwritingDao.create({
archive_id: archiveId,
source: 'demo',
image_path: '', // 演示数据无图片
ocr_text: p.text,
feature_json: JSON.stringify(p.scores),
created_at: timestamp
})
6.2 图片缺失的处理
在 UI 层展示时,需要处理图片路径为空的情况:
if (imagePath && imagePath !== '') {
Image(imagePath)
.width(120).height(120)
.objectFit(ImageFit.Contain)
} else {
// 无图片的占位符
Text('📝')
.fontSize(40)
}
七、权限声明
在 module.json5 中声明文件读写权限(虽然 cache 目录不需要额外权限):
{
"requestPermissions": [
// cache 目录读写不需要额外权限
// 但访问 files 目录需要:
// {
// "name": "ohos.permission.WRITE_MEDIA",
// ...
// }
]
}
提示:应用私有的
cache/和files/目录不需要额外权限即可读写。只有访问系统相册或其他应用的共享文件时才需要媒体权限。
八、文件管理最佳实践
8.1 检查清单
| 检查项 | 项目中的做法 |
|---|---|
| 是否使用 cache 而非 files | ✅ 临时照片存 cache |
| 文件是否有关联记录在 DB | ✅ HandwritingDao 存 image_path |
| 是否有文件清理策略 | ✅ 可定期清理 7 天前的缓存 |
| 是否在组件销毁时释放文件句柄 | ✅ fs.closeSync 立即关闭 |
| 文件名是否唯一 | ✅ ${Date.now()} 时间戳方案 |
总结
本文解析了鸿蒙应用中图片压缩与本地存储的完整方案:
- 图片压缩:
ImagePacker.packing()+quality=80+desiredSize=1080p - 存储位置:
cache/目录存储临时拍摄照片,DB存储路径引用 - 文件清理:定期清理 7 天前的旧缓存文件
- 路径管理:不同来源的图片路径统一通过 AppStorage 和 DB 传递
- 权限策略:cache 目录不需要额外权限
下一篇文章将进入 NFC 碰一碰绑定 模块。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
参考资源:
- fileIo 文件管理 API
- ImagePacker 图片编码
- @kit.CoreFileKit 模块
- @kit.ImageKit 模块
- 鸿蒙应用目录结构
- statfs 磁盘空间查询
- HarmonyOS 开发文档
七、文件存储最佳实践
7.1 目录结构规划
鸿蒙应用的文件存储遵循沙箱隔离原则,常用目录说明:
| 目录 | 路径 | 用途 | 特点 |
|---|---|---|---|
| cacheDir | /data/storage/el2/base/cache |
图片缓存 | 可被系统清理 |
| filesDir | /data/storage/el2/base/files |
持久化文件 | 不会被自动清理 |
| tempDir | /data/storage/el2/base/temp |
临时文件 | 应用退出后清理 |
const context = getContext(this)
// 持久化存储(不被系统清理)
const persistentPath = `${context.filesDir}/images/`
// 缓存存储(可被系统清理)
const cachePath = `${context.cacheDir}/thumbnails/`
7.2 文件命名策略
// ✅ 好的命名:包含时间戳 + 类型 + 后缀
const fileName = `handwriting_${userId}_${Date.now()}.jpg`
// ❌ 避免的命名:容易冲突
const badName = `photo.jpg` // 多次拍照会覆盖
const badName2 = `image_${Math.random()}.jpg` // 随机数可能重复
7.3 缓存清理策略
// 定期清理超过 7 天的缓存图片
async function cleanOldCache(context: common.UIAbilityContext): Promise<void> {
const cacheDir = context.cacheDir
const files = fs.listFileSync(cacheDir)
const now = Date.now()
const sevenDaysMs = 7 * 24 * 60 * 60 * 1000
for (const file of files) {
const filePath = `${cacheDir}/${file}`
const stat = fs.statSync(filePath)
if (now - stat.mtime * 1000 > sevenDaysMs) {
fs.unlinkSync(filePath)
hilog.info(0x0000, 'Cache', `已清理过期缓存: ${file}`)
}
}
}
7.4 压缩质量参考指南
| quality 值 | 文件大小(相对原始) | 视觉质量 | 推荐场景 |
|---|---|---|---|
| 100 | 50-80% | 无损失 | 原图存档 |
| 85 | 30-50% | 几乎无差异 | ✅ 本项目选用 |
| 70 | 20-35% | 轻微压缩 | 网络传输 |
| 50 | 10-20% | 明显压缩 | 缩略图 |
| 30 | 5-10% | 明显失真 | 极小文件 |
7.5 磁盘空间检查
在写入文件前,建议先检查磁盘可用空间:
import statfs from '@ohos.file.statvfs'
async function checkDiskSpace(path: string, requiredBytes: number): Promise<boolean> {
try {
const stat = await statfs.statvfs(path)
const freeBytes = stat.bfree * stat.bsize
if (freeBytes < requiredBytes) {
hilog.warn(0x0000, 'Storage', `磁盘空间不足: 可用 ${freeBytes} bytes,需要 ${requiredBytes} bytes`)
return false
}
return true
} catch (err) {
hilog.error(0x0000, 'Storage', '检查磁盘空间失败: %s', JSON.stringify(err))
return true // 检查失败时允许继续
}
}
// 使用示例
const hasSpace = await checkDiskSpace(context.cacheDir, 5 * 1024 * 1024) // 需要 5MB
if (!hasSpace) {
this.showToast('存储空间不足,请清理缓存后重试')
return
}
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
更多推荐

所有评论(0)