38 图片压缩与本地存储文件管理与缓存策略前言图38 图片压缩与本地存储文件管理与缓存策略 运行效果截图HarmonyOS NEXT拍照或从相册选取图片后原始图片通常体积较大单张可达 10MB如果不经过压缩和处理就直接存储不仅浪费存储空间还可能影响 App 的读写性能。本文解析鸿蒙应用中的图片压缩策略、文件存储管理和缓存清理机制。鸿蒙官方·文件管理文档developer.huawei.com项目源码仓库harmony-app GitHub图图片压缩与本地存储全流程——ImagePacker 编码 → fileIo 写入 → 缓存管理PixelMapImagePackercreateImagePackerpacking 编码formatqualityArrayBuffer二进制数据fs.openSync创建文件fs.writeSync写入数据fs.closeSync关闭文件本地文件路径传入 AnalyzingPage一、图片压缩策略1.1 使用 ImagePacker 压缩import{image}fromkit.ImageKitimport{fileIoasfs}fromkit.CoreFileKitasyncfunctioncompressImage(inputPath:string,outputPath:string):Promisevoid{// 1. 加载原图constsourceimage.createImageSource(inputPath)constpixelMapawaitsource.createPixelMap({desiredPixelFormat:image.ImagePixelFormat.RGBA_8888,desiredSize:{width:1080,height:1440}// 限制最大分辨率})// 2. 压缩编码constpackerimage.createImagePacker()constpackedImageawaitpacker.packing(pixelMap,{format:image/jpeg,quality:80// 压缩质量 (0-100)})// 3. 写入文件constfilefs.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.5MBAI 分析输入60-75中200-600KB缩略图预览30-50低50-200KB网络传输1.3 压缩效果估算// 原始照片后置摄像头≈ 12MP → 约 5-15MB// 压缩到 1080p quality80 → 约 500KB-1.5MB// 压缩率约 90%二、文件存储位置2.1 应用目录结构鸿蒙应用的文件存储有以下几个主要目录目录路径用途特点存储策略.../cache/缓存目录系统可随时清理✅ 临时照片.../files/文件目录持久存储❌ 暂不使用.../database/数据库目录自动管理RDB 存储位置2.2 项目中的文件存储// 拍照后临时保存到缓存目录constcacheDir/data/storage/el2/base/cache/constfileNamecapture_${Date.now()}.jpgconstfilePath${cacheDir}${fileName}// 保存照片// 在 DAO 中只存储路径awaitHandwritingDao.create({image_path:filePath,// 图片路径// ...})2.3 缓存 vs 持久存储对比缓存 (cache)持久 (files)写入本项目的文件拍照原始文件数据库备份清理条件App 设置清缓存、系统空间不足手动删除路径.../cache/.../files/本项目存储内容临时拍摄照片RDB 数据库文件三、文件生命周期管理3.1 文件创建// CapturePage — 拍照时创建constfilePath/data/storage/el2/base/cache/capture_${Date.now()}.jpg// 保存到 DB 供其他页面使用AppStorage.setOrCreatestring(capture_image_path,filePath)3.2 文件使用跨页面// AnalyzingPage — 读取路径constimagePathAppStorage.getstring(capture_image_path)??// 写入 DBawaitHandwritingDao.create({image_path:imagePath,// ...})3.3 文件清理策略// 清理旧缓存文件asyncfunctioncleanOldCache(maxAgeDays:number7):Promisevoid{constcacheDir/data/storage/el2/base/cache/constnowDate.now()constmaxAgemaxAgeDays*86400000try{// 列出缓存目录文件constfilesfs.listFileSync(cacheDir)for(constfileNameoffiles){if(fileName.startsWith(capture_)){constfilePath${cacheDir}${fileName}conststatfs.statSync(filePath)if(now-stat.mtimemaxAge){fs.unlinkSync(filePath)// 删除旧文件hilog.info(0x0000,FileManager,清理旧缓存: %s,filePath)}}}}catch(e){hilog.error(0x0000,FileManager,清理缓存失败: %s,JSON.stringify(e))}}四、文件操作 API4.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~800MBRDB 数据库~5KB~5MBApp 代码-~20MB5.2 空间检查import{statfs}fromkit.CoreFileKitasyncfunctioncheckStorage():Promise{free:number,total:number}{conststatstatfs.getFreeBytes(/data/storage/el2/base/)consttotalstatfs.getTotalBytes(/data/storage/el2/base/)return{free:stat/(1024*1024),// 转换为 MBtotal:total/(1024*1024)}}// 使用示例constspaceawaitcheckStorage()if(space.free100){// 剩余空间不足 100MB提示用户清理}六、非相机图片的存储6.1 Demo 种子数据DemoSeeder 生成的演示数据中image_path为空字符串因为不是真实拍摄的awaitHandwritingDao.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(imagePathimagePath!){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()quality80desiredSize1080p存储位置cache/目录存储临时拍摄照片DB存储路径引用文件清理定期清理 7 天前的旧缓存文件路径管理不同来源的图片路径统一通过 AppStorage 和 DB 传递权限策略cache 目录不需要额外权限下一篇文章将进入NFC 碰一碰绑定模块。如果这篇文章对你有帮助欢迎点赞、收藏⭐、关注你的支持是我持续创作的动力参考资源fileIo 文件管理 APIImagePacker 图片编码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临时文件应用退出后清理constcontextgetContext(this)// 持久化存储不被系统清理constpersistentPath${context.filesDir}/images/// 缓存存储可被系统清理constcachePath${context.cacheDir}/thumbnails/7.2 文件命名策略// ✅ 好的命名包含时间戳 类型 后缀constfileNamehandwriting_${userId}_${Date.now()}.jpg// ❌ 避免的命名容易冲突constbadNamephoto.jpg// 多次拍照会覆盖constbadName2image_${Math.random()}.jpg// 随机数可能重复7.3 缓存清理策略// 定期清理超过 7 天的缓存图片asyncfunctioncleanOldCache(context:common.UIAbilityContext):Promisevoid{constcacheDircontext.cacheDirconstfilesfs.listFileSync(cacheDir)constnowDate.now()constsevenDaysMs7*24*60*60*1000for(constfileoffiles){constfilePath${cacheDir}/${file}conststatfs.statSync(filePath)if(now-stat.mtime*1000sevenDaysMs){fs.unlinkSync(filePath)hilog.info(0x0000,Cache,已清理过期缓存:${file})}}}7.4 压缩质量参考指南quality 值文件大小相对原始视觉质量推荐场景10050-80%无损失原图存档8530-50%几乎无差异✅ 本项目选用7020-35%轻微压缩网络传输5010-20%明显压缩缩略图305-10%明显失真极小文件7.5 磁盘空间检查在写入文件前建议先检查磁盘可用空间importstatfsfromohos.file.statvfsasyncfunctioncheckDiskSpace(path:string,requiredBytes:number):Promiseboolean{try{conststatawaitstatfs.statvfs(path)constfreeBytesstat.bfree*stat.bsizeif(freeBytesrequiredBytes){hilog.warn(0x0000,Storage,磁盘空间不足: 可用${freeBytes}bytes需要${requiredBytes}bytes)returnfalse}returntrue}catch(err){hilog.error(0x0000,Storage,检查磁盘空间失败: %s,JSON.stringify(err))returntrue// 检查失败时允许继续}}// 使用示例consthasSpaceawaitcheckDiskSpace(context.cacheDir,5*1024*1024)// 需要 5MBif(!hasSpace){this.showToast(存储空间不足请清理缓存后重试)return}如果这篇文章对你有帮助欢迎点赞、收藏⭐、关注你的支持是我持续创作的动力