diff --git a/CHANGELOG.md b/CHANGELOG.md index 218ba8b..6a05295 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 3.0.0-rc.1 +- 新增从内存或文件缓存获取图片数据接口isUrlExist +- 新增图片预加载preLoadCache并会犯文件缓存路径 +- ImageKnifeOption新增writeCacheStrategy存入策略 +- ImageKnifeOption新增onlyRetrieveFromCache仅用缓存加载 + ## 3.0.0-rc.0 使用Image组件替换Canvas组件渲染,并重构大部分的实现逻辑,提升渲染性能 diff --git a/README.md b/README.md index 0aa52b9..d4f6b26 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,28 @@ ImageKnifeComponent({ ImageKnifeOption: } }).width(100).height(100) ``` +## 接口说明 +### ImageKnifeOption参数列表 +| 参数名称 | 入参内容 | 功能简介 | +|-----------------------|--------------------------------|---------------| +| loadSrc | string、PixelMap、Resource | 主图展示 | +| placeholderSrc | PixelMap、Resource | 占位图图展示(可选) | +| errorholderSrc | PixelMap、Resource | 错误图展示(可选) | +| objectFit | ImageFit | 图片展示样式(可选) | +| writeCacheStrategy | WriteCacheStrategy_Type | 写入缓存策略(可选) | +| onlyRetrieveFromCache | boolean | 仅使用缓存加载数据(可选) | +| customGetImage | (context: Context, src: string | 自定义网络(可选) | | Resource | 错误占位图数据源 | +| border | BorderOptions | 边框圆角(可选) | +| priority | taskpool.Priority | 加载优先级(可选) | +| context | common.UIAbilityContext | 上下文(可选) | +| progressListener | (progress: number)=>void | 进度(可选) | + +### ImageKnife接口 +| 参数名称 | 入参内容 | 功能简介 | +|--------------|------------------------------------|---------------| +| preLoadCache | url:string | 预加载并返回文件缓存路径 | +| isUrlExist | url: string, cacheType: Cache_Type | 从内存或文件缓存中获取资源 | ## 约束与限制 API11 diff --git a/entry/src/main/ets/pages/Index.ets b/entry/src/main/ets/pages/Index.ets index 71da2ac..5adf8bb 100644 --- a/entry/src/main/ets/pages/Index.ets +++ b/entry/src/main/ets/pages/Index.ets @@ -62,7 +62,18 @@ struct Index { }); }) + Button("测试文件缓存预加载").onClick(()=>{ + router.push({ + uri: 'pages/TestPrefetchToFileCache', + }); + }) + Button("测试获取内存文件缓存").onClick(()=>{ + router.push({ + uri: 'pages/TestIsUrlExist', + + }); + }) } .width('100%') diff --git a/entry/src/main/ets/pages/ListPage.ets b/entry/src/main/ets/pages/ListPage.ets index d74fbcf..da98504 100644 --- a/entry/src/main/ets/pages/ListPage.ets +++ b/entry/src/main/ets/pages/ListPage.ets @@ -18,20 +18,20 @@ import { ImageKnifeComponent, ImageKnifeOption } from '@ohos/imageknife'; @Component struct ListPage { - private datas: string[] = [] + private data: string[] = [] @State ImageKnifeOption: ImageKnifeOption = { loadSrc: $r('app.media.startIcon')} aboutToAppear(): void { for (let i = 0; i < 1000; i++) { - this.datas.push(i.toString()) + this.data.push(i.toString()) } } build() { Row() { List({ space: 10 }) { - ForEach(this.datas, (item: string) => { + ForEach(this.data, (item: string) => { ImageKnifeComponent({ ImageKnifeOption: this.ImageKnifeOption }).height(200).width(200) }, (item: string) => item) } diff --git a/entry/src/main/ets/pages/TestIsUrlExist.ets b/entry/src/main/ets/pages/TestIsUrlExist.ets new file mode 100644 index 0000000..3f47b64 --- /dev/null +++ b/entry/src/main/ets/pages/TestIsUrlExist.ets @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ImageKnifeComponent,ImageKnife,ImageKnifeOption,ImageKnifeData,Cache_Type } from '@ohos/imageknife' + +@Entry +@Component +struct TestIsUrlExist { + @State imageKnifeOption: ImageKnifeOption = { + loadSrc:$r('app.media.startIcon'), + placeholderSrc:$r('app.media.loading') + } + @State source: PixelMap | string = "" + @State source1: PixelMap | string = "" + build() { + Column() { + Flex(){ + Button("加载gif图").onClick(()=>{ + this.imageKnifeOption = "https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658" + }) + Button("内存缓存获取gif").onClick(()=>{ + ImageKnife.getInstance() + .isUrlExist("https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658",Cache_Type.MemoryCache) + .then((data)=>{ + this.source = data!.source + }) + }) + Button("文件缓存获取gif").onClick(()=>{ + ImageKnife.getInstance() + .isUrlExist("https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658",Cache_Type.FileCache) + .then((data)=>{ + this.source1 = data!.source + }) + }) + } + Flex(){ + Button("加载静态图").onClick(()=>{ + this.imageKnifeOption = 'https://hbimg.huabanimg.com/95a6d37a39aa0b70d48fa18dc7df8309e2e0e8e85571e-x4hhks_fw658/format/webp' + }) + Button("内存缓存获取").onClick(()=>{ + ImageKnife.getInstance() + .isUrlExist('https://hbimg.huabanimg.com/95a6d37a39aa0b70d48fa18dc7df8309e2e0e8e85571e-x4hhks_fw658/format/webp',Cache_Type.MemoryCache) + .then((data)=>{ + this.source = data!.source + }) + }) + Button("文件缓存获取").onClick(()=>{ + ImageKnife.getInstance() + .isUrlExist('https://hbimg.huabanimg.com/95a6d37a39aa0b70d48fa18dc7df8309e2e0e8e85571e-x4hhks_fw658/format/webp',Cache_Type.FileCache) + .then((data)=>{ + this.source1 = data!.source + }) + }) + } + + ImageKnifeComponent({ + ImageKnifeOption: this.imageKnifeOption + }).width(200).height(200) + Image(this.source) + .width(200).height(200).backgroundColor(Color.Pink) + Image(this.source1) + .width(200).height(200).backgroundColor(Color.Pink) + } + .height('100%') .width('100%') + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/TestPrefetchToFileCache.ets b/entry/src/main/ets/pages/TestPrefetchToFileCache.ets new file mode 100644 index 0000000..a370fdc --- /dev/null +++ b/entry/src/main/ets/pages/TestPrefetchToFileCache.ets @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2024 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the 'License'); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an 'AS IS' BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ImageKnifeComponent,ImageKnife,ImageKnifeOption } from '@ohos/imageknife' + +@Entry +@Component +struct TestPrefetchToFileCachePage { + @State imageKnifeOption: ImageKnifeOption = { + loadSrc:$r('app.media.startIcon'), + placeholderSrc:$r('app.media.loading') + } + async preload(url:string) { + let fileCachePath = await ImageKnife.getInstance().preLoadCache(url) + console.log("preload-fileCachePath=="+ fileCachePath) + } + build() { + Column() { + Button("磁盘预加载").onClick(async ()=>{ + await this.preload("https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658") + }) + Button("加载图片").onClick(()=>{ + this.imageKnifeOption.loadSrc = "https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658" + }) + ImageKnifeComponent({ + ImageKnifeOption: this.imageKnifeOption + }).width(300).height(300) + } + .height('100%') .width('100%') + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/User.ets b/entry/src/main/ets/pages/User.ets index e58319b..b724078 100644 --- a/entry/src/main/ets/pages/User.ets +++ b/entry/src/main/ets/pages/User.ets @@ -16,7 +16,7 @@ export struct UserAvatar { imgSizes: number = 1 @State ImageKnifeOption: ImageKnifeOption = new ImageKnifeOption() @StorageProp('WeLink_Mob_fontSize_multiple') @Watch('updateImgSize') WeLink_Mob_fontSize_multiple: number = 0 - scaleable: boolean = true; + scalable: boolean = true; @State calcImgSize: number = 100 aboutToAppear(): void { @@ -25,7 +25,7 @@ export struct UserAvatar { } setImageSize() { - if (!this.scaleable) { + if (!this.scalable) { this.calcImgSize = this.imgSize } else if (this.WeLink_Mob_fontSize_multiple < 0.9) { this.calcImgSize = this.imgSize * 0.9 @@ -72,12 +72,12 @@ export struct UserAvatar { - // Image(this.userInfo) - // Text((this.imageKnifeOption.loadSrc as string).split('/')[8]) - // .position({ x: 0, y: 0 }) - // .zIndex(9) - // .fontSize(12) - // .fontColor('#ff0000') + // Image(this.userInfo) + // Text((this.imageKnifeOption.loadSrc as string).split('/')[8]) + // .position({ x: 0, y: 0 }) + // .zIndex(9) + // .fontSize(12) + // .fontColor('#ff0000') } } } \ No newline at end of file diff --git a/entry/src/main/resources/base/profile/main_pages.json b/entry/src/main/resources/base/profile/main_pages.json index 5b02dd4..edb3146 100644 --- a/entry/src/main/resources/base/profile/main_pages.json +++ b/entry/src/main/resources/base/profile/main_pages.json @@ -7,6 +7,8 @@ "pages/LongImagePage", "pages/TransformPage", "pages/UserPage", - "pages/TestImageFlash" + "pages/TestImageFlash", + "pages/TestPrefetchToFileCache", + "pages/TestIsUrlExist" ] } \ No newline at end of file diff --git a/library/index.ets b/library/index.ets index b6c20fd..4eb47af 100644 --- a/library/index.ets +++ b/library/index.ets @@ -10,3 +10,5 @@ export { FileUtils } from './src/main/ets/utils/FileUtils' export { LogUtil } from './src/main/ets/utils/LogUtil' +export { ImageKnifeData , Cache_Type} "./src/maim/ets/model/ImageKnifeData" + diff --git a/library/oh-package.json5 b/library/oh-package.json5 index 0b53565..2baf06a 100644 --- a/library/oh-package.json5 +++ b/library/oh-package.json5 @@ -14,7 +14,7 @@ "main": "index.ets", "repository": "https://gitee.com/openharmony-tpc/ImageKnife", "type": "module", - "version": "3.0.0-rc.0", + "version": "3.0.0-rc.1", "dependencies": { }, diff --git a/library/src/main/ets/ImageKnife.ets b/library/src/main/ets/ImageKnife.ets index c529d4b..df56c97 100644 --- a/library/src/main/ets/ImageKnife.ets +++ b/library/src/main/ets/ImageKnife.ets @@ -13,11 +13,14 @@ * limitations under the License. */ import { ImageKnifeRequest } from './ImageKnifeRequest'; -import { ImageKnifeData } from './model/ImageKnifeData'; +import { ImageKnifeData,Cache_Type } from './model/ImageKnifeData'; import { MemoryLruCache } from './utils/MemoryLruCache'; import { IMemoryCache } from './utils/IMemoryCache' import { FileCache } from './utils/FileCache'; import { ImageKnifeDispatcher } from './ImageKnifeDispatcher'; +import { ImageKnifeOption } from './ImageKnifeOption'; +import { Tools } from './utils/Tools'; +import { common } from '@kit.AbilityKit'; export class ImageKnife { @@ -84,6 +87,86 @@ export class ImageKnife { saveFileCache(key: string, data: ArrayBuffer): void { this.fileCache?.put(key, data) } + getFileCache(): FileCache{ + return this.fileCache as FileCache + } + // 预加载到文件缓存,并返回缓存路径 + preLoadCache(url:string): Promise { + return new Promise((resolve,reject)=>{ + let imageKnifeOption = new ImageKnifeOption() + imageKnifeOption.loadSrc = url + let keys = Tools.generateKey(url) + let cachePath = ImageKnife.getInstance().getFileCache().getFileToPath(keys) + if(cachePath == null || cachePath == "" || cachePath == undefined) { + let request = new ImageKnifeRequest( + imageKnifeOption, + imageKnifeOption.context !== undefined ? imageKnifeOption.context : getContext(this) as common.UIAbilityContext, + 0, + 0, + 0, + { + showPixelMap(version: number, pixelMap: PixelMap | string) { + let cachePaths = ImageKnife.getInstance().getFileCache().getFileToPath(keys) + if(cachePaths != "") { + resolve(cachePaths) + } else { + reject(undefined) + } + } + } + ) + this.execute(request) + } else { + resolve(cachePath) + } + }) + } + // 从内存或文件缓存中获取资源 + isUrlExist(url: string, cacheType: Cache_Type):Promise { + return new Promise((resolve,reject)=>{ + if(cacheType == Cache_Type.MemoryCache) { + let keys = Tools.generateMemoryKey(url) + let memoryCache:ImageKnifeData | undefined = ImageKnife.getInstance() + .loadFromMemoryCache(keys) + resolve(memoryCache) + } else if (cacheType == Cache_Type.FileCache) { + let keys = Tools.generateKey(url) + let buffer = ImageKnife.getInstance().loadFromFileCache(keys) + if(buffer != undefined) { + let fileTypeUtil = new FileTypeUtil(); + let typeValue = fileTypeUtil.getFileType(buffer); + if (typeValue === 'gif' || typeValue === 'webp') { + let base64Help = new util.Base64Helper() + + let base64str = "data:image/" + typeValue + ";base64," + base64Help.encodeToStringSync(new Uint8Array(buffer)) + resolve({ + source:base64str, + imageWidth: 0, + imageHeight: 0 + }) + } + + let imageSource: image.ImageSource = image.createImageSource(buffer); + let decodingOptions: image.DecodingOptions = { + editable: true, + } + + let resPixelmap: PixelMap | undefined = undefined + imageSource.createPixelMap(decodingOptions) + .then((pixelmap: PixelMap) => { + resolve({ + source:pixelmap, + imageWidth: 0, + imageHeight: 0 + }) + imageSource.release() + }) + } else { + resolve(undefined) + } + } + }) + } /** * 清除所有文件缓存 diff --git a/library/src/main/ets/ImageKnifeDispatcher.ets b/library/src/main/ets/ImageKnifeDispatcher.ets index 2befcab..3ba1b5a 100644 --- a/library/src/main/ets/ImageKnifeDispatcher.ets +++ b/library/src/main/ets/ImageKnifeDispatcher.ets @@ -33,6 +33,7 @@ import { FileTypeUtil } from './utils/FileTypeUtil'; import util from '@ohos.util'; import { Tools } from './utils/Tools'; import { SparkMD5 } from './3rd_party/sparkmd5/spark-md5'; +import { WriteCacheStrategy_Type } "./ImageKnifeOption" export class ImageKnifeDispatcher { // 最大并发 @@ -121,7 +122,9 @@ export class ImageKnifeDispatcher { context: currentRequest.context, src: imageSrc, key: key, - customGetImage: currentRequest.ImageKnifeOption.customGetImage + customGetImage: currentRequest.ImageKnifeOption.customGetImage, + onlyRetrieveFromCache: currentRequest.ImageKnifeOption.onlyRetrieveFromCache, + requestSource } // 启动线程下载和解码主图 let task = new taskpool.Task(requestJob, request) @@ -164,7 +167,9 @@ export class ImageKnifeDispatcher { } // 保存内存缓存 - ImageKnife.getInstance().saveMemoryCache(Tools.generateMemoryKey(imageSrc), ImageKnifeData) + if(currentRequest.ImageKnifeOption.writeCacheStrategy == WriteCacheStrategy_Type.OnlyFile) { + ImageKnife.getInstance().saveMemoryCache(Tools.generateMemoryKey(imageSrc), ImageKnifeData) + } if (requestList !== undefined) { @@ -250,7 +255,7 @@ async function requestJob(request: RequestJobRequest): Promise Promise + customGetImage?: (context: Context, src: string | PixelMap | Resource) => Promise, + onlyRetrieveFromCache?: boolean + requestSource:ImageKnifeRequestSource } diff --git a/library/src/main/ets/ImageKnifeOption.ets b/library/src/main/ets/ImageKnifeOption.ets index ddae520..f415f39 100644 --- a/library/src/main/ets/ImageKnifeOption.ets +++ b/library/src/main/ets/ImageKnifeOption.ets @@ -14,7 +14,10 @@ */ import taskpool from '@ohos.taskpool'; import common from '@ohos.app.ability.common' - +export enum WriteCacheStrategy_Type { + DEFAULT = 0,// 默认都开启 + OnlyFile = 1 // 仅缓存文件 +} @Observed export class ImageKnifeOption { // 主图资源 @@ -25,6 +28,10 @@ export class ImageKnifeOption { errorholderSrc?: PixelMap | Resource; objectFit?: ImageFit + // 缓存策略 + writeCacheStrategy?: WriteCacheStrategy_Type + // 仅使用缓存加载数据 + onlyRetrieveFromCache?: boolean = false; customGetImage?: (context: Context, src: string | PixelMap | Resource) => Promise diff --git a/library/src/main/ets/model/ImageKnifeData.ets b/library/src/main/ets/model/ImageKnifeData.ets index eece771..41acad8 100644 --- a/library/src/main/ets/model/ImageKnifeData.ets +++ b/library/src/main/ets/model/ImageKnifeData.ets @@ -18,3 +18,10 @@ export interface ImageKnifeData { imageHeight: number } +export enum Cache_Type { + // 内存缓存 + MemoryCache = 0, + // 文件缓存 + FileCache = 1 +} + diff --git a/library/src/main/ets/utils/FileCache.ets b/library/src/main/ets/utils/FileCache.ets index 195a0ad..ac018ea 100644 --- a/library/src/main/ets/utils/FileCache.ets +++ b/library/src/main/ets/utils/FileCache.ets @@ -172,8 +172,8 @@ export class FileCache { let remove: number | undefined = this.lruCache.remove(key) if (remove !== undefined) { - FileUtils.getInstance().deleteFile(this.path + key) - this.removeMemorySize(remove) + FileUtils.getInstance().deleteFile(this.path + key) + this.removeMemorySize(remove) } } @@ -258,4 +258,20 @@ export class FileCache { return FileUtils.getInstance() .readFileSync(context.cacheDir + FileUtils.SEPARATOR + FileCache.CACHE_FOLDER + FileUtils.SEPARATOR + key) } + /** + * 获取key缓存数据绝对路径 + * + * @params key 数值 + */ + getFileToPath(key: string): string { + if(!!!key) { + throw new Error("key is null,checking the parameter") + } + let path = this.path + key + if(FileUtils.getInstance().exist(path)) { + return path + } else { + return "" + } + } } \ No newline at end of file