移动端开发过程中图片缓存功能是必备,iOS和安卓都有相关工具库,鸿蒙系统组件本身也自带缓存功能,但是遇到复杂得逻辑功能还是需要封装图片缓存工具。
系统组件Image
- Image的缓存策略
Image模块提供了三级Cache机制,解码后内存图片缓存、解码前数据缓存、物理磁盘缓存。在加载图片时会逐级查找,如果在Cache中找到之前加载过的图片则提前返回对应的结果。
- Image组件如何配置打开和关闭缓存
- 内存图片缓存:通过setImageCacheCount接口打开缓存,如果希望每次联网都获取最新资源,可以不设置或设置为0不缓存。
- 磁盘缓存:磁盘缓存是默认开启的,默认值为100M,可以将setImageFileCacheSize的值设置为0关闭磁盘缓存。
- 解码前数据缓存:通过setImageRawDataCacheSize设置内存中缓存解码前图片数据的大小上限,单位为字节,提升再次加载同源图片的加载速度。如果不设置则默认为0,不进行缓存
TypeScript
import app, { AppResponse } from '@system.app';
@Entry
@Component
struct Home {
url: string = '';
aboutToAppear(): void {
app.setImageCacheCount(6)//内存缓存数量
app.setImageRawDataCacheSize(10 * 1024 * 1024)//10M
app.setImageFileCacheSize(100 * 1024 * 1024) //磁盘缓存 100M
}
build() {
Column() {
Image(this.url)
}
.height('100%')
.width('100%')
}
}
鸿蒙官方明确:setImageCacheCount、setImageRawDataCacheSize和setImageFileCacheSize这这三个图片缓存接口灵活性不足,后续不再更新
封装图片缓存库
由于系统图片缓存不好用,尝试封装图片下载和缓存工具。下载使用多线程taskTool子线程分发下载任务,使用request.downloadFile具体执行下载。内存缓存使用使magLruCache会根据使用图片频率,自动删除使用率低的缓存内存图片。磁盘使用文件存储。
图片获取逻辑
从内存缓存中取图片,如果没有再从磁盘文件中取图片,都没有则开启现在任务下载
TypeScript
/**
* 文件下载并拿到缓存返回给调用者,如果下载和图片解析失败返回链接,通过下载链接进行加载,利用系统缓存机制进行加载。
* @param context 上下文
* @param downloadUrl 图片地址
* @param downloadPath 缓存地址
* @returns 返回PixelMap 失败返回downloadUrl
*/
downloadCachedFile(
downloadUrl: string
): Promise<image.PixelMap | string> {
return new Promise(async (resolve, _) => {
try {
//1、获取图片类型例如 .png/.jpg/.weep等。
//目前不用图片类型,应为图片链接可能无任何类型信息。
//2、获取MD5摘要
let downloadUrlMd5 = await new Md5Util().doMd(downloadUrl)
//3、获取图片文件名称
let newImageFileName = this.getImageFileName(downloadUrlMd5)
//4、拼接下载路径地址
let downloadPath = this.dir + this.separator + newImageFileName
//5、检测图片文件夹是否存在,否->创建
let existedBefore = FileUtil.checkAndMkdirSync(this.dir)
//之前存在才应该去遍历文件,然后更新文件
if (existedBefore) {
//判断此图片资源是否已经被缓存到了文件夹下面
let findFileExistInFolder = await this.findFileNameInFolder(this.dir, downloadUrlMd5)
//如果存在去更新文件名称,否则去下载
if (findFileExistInFolder[0]) {
newImageFileName = await this.renameOfImageByDataTime(findFileExistInFolder[1], newImageFileName)
downloadPath = this.dir + this.separator + newImageFileName
//加入gif判断获取缓存,名称改变,对应的缓存需要更新到缓存里面
let type = await this.getImageType(downloadPath)
if (this.GIF_IMAG_TYPES.includes(type)){
const gifUri = this.URI_HEAD + downloadPath
ImageCacheUtil.imagLruCache.put(downloadUrlMd5, gifUri)
}
//内存中取
let mapCachePixMap = ImageCacheUtil.imagLruCache.get(downloadUrlMd5)
if (mapCachePixMap) {
resolve(mapCachePixMap)
return
}
//磁盘文件中取
let pixelMap = await this.uriOrPathConvertPixelMap(downloadPath)
if (pixelMap) {
ImageCacheUtil.imagLruCache.put(downloadUrlMd5, pixelMap)
resolve(pixelMap)
return
}
}
}
//如果图片文件夹不存在就去下载
this.downloadFile(this.context,downloadUrlMd5, downloadUrl, downloadPath).then((resultEnd) => {
resolve(resultEnd)
return
}).catch((_: BusinessError) => {
resolve(downloadUrl)
return
})
} catch (err) {
console.error(this.TAG + `::downLoadCacheFile error message = ${err.stack?.toString()}`)
resolve(downloadUrl)
return
}
})
}
request.downloadFile下载图片文件解析成pixMap
TypeScript
/**
* 网络下载图片方法
* @param context
* @param downloadUrl
* @param downloadPath
* @returns
*/
private async downloadFile(context: Context,downloadUrlMd5:string, downloadUrl: string,
downloadPath: string): Promise<image.PixelMap | string> {
return new Promise(async (resolve, reject) => {
//6、检测文件夹大小如果大于200M,就需要删除一半的图片资源
this.clearHalfCache(this.dir)
request.downloadFile(context, { url: downloadUrl, filePath: downloadPath })
.then((downloadTask: request.DownloadTask) => {
downloadTask.on('complete', async () => {
let type = await this.getImageType(downloadPath)
if (this.noCatchImageType.includes(type)) {
this.clearImageFailCache(downloadPath)
resolve(downloadUrl);
return
}
if (this.GIF_IMAG_TYPES.includes(type)) {
const fileUri = this.URI_HEAD + downloadPath
ImageCacheUtil.imagLruCache.put(downloadUrlMd5, fileUri)
resolve(fileUri)
return
}
let catchPixelMap = await this.uriOrPathConvertPixelMap(downloadPath);
if (catchPixelMap) {
ImageCacheUtil.imagLruCache.put(downloadUrlMd5, catchPixelMap)
resolve(catchPixelMap)
return
}
reject(new Error('PixelMap编码失败'));
});
downloadTask.on('fail', (error) => {
console.error(this.TAG + `::downloadFile downloadTask fail error= ${error?.toString()}`)
//下载失败需要删除本地路径,然后返回下载缓存路径
this.clearImageFailCache(downloadPath);
reject(new Error('图片下载失败'));
});
})
.catch((err: BusinessError) => {
console.error(`${this.TAG}::downloadFile Failed to request the download. Code: ${err.code}, message: ${err.message}`);
this.clearImageFailCache(downloadPath);
reject("err message:" + err.message + ",error code:" + err.code)
});
})
}