diff --git a/README.md b/README.md index 327873a..74f9c1d 100644 --- a/README.md +++ b/README.md @@ -333,6 +333,7 @@ request.skipMemoryCache(true) | preload(request: RequestOption) | request: RequestOption | 根据用户配置参数具体执行预加载流程 | | pauseRequests() | | 全局暂停请求 | | resumeRequests() | | 全局恢复暂停 | +| isUrlExist(request: RequestOption) | request: RequestOption | 判断图片是否在 缓存和磁盘中存在,request.setCacheType(CacheType.Cache) request.setImageViewSize(this.comSize) 如果入参是缓存,需要传入值图片大小| ### 缓存策略相关 @@ -368,6 +369,15 @@ request.skipMemoryCache(true) | ScaleType.AUTO_WIDTH | int | 设置高的时候,图片宽度自适应 | | ScaleType.AUTO | int | 没有设置宽和高,图片按照自身宽高显示 | + +### CacheType类型展示效果 + +| 使用方法 | 类型 | 策略描述 | +|-------------------------|-----|-----------------------------------| +| CacheType.Cache| int | 缓存 | +| CacheType.Disk | int | 磁盘 | + + ### 图片变换相关 | 使用方法 | 类型 | 相关描述 | diff --git a/entry/src/main/ets/pages/testImageKnifeCache.ets b/entry/src/main/ets/pages/testImageKnifeCache.ets new file mode 100644 index 0000000..8988a9a --- /dev/null +++ b/entry/src/main/ets/pages/testImageKnifeCache.ets @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2022 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, + ImageKnifeOption, + ImageKnifeGlobal, + ImageKnife, + ImageKnifeData, + RequestOption, + Size +} from '@ohos/libraryimageknife' +import image from '@ohos.multimedia.image'; +import { BusinessError } from '@ohos.base'; +import { CacheType } from '@ohos/imageknife/src/main/ets/components/imageknife/RequestOption'; + + +let imageKnife: ImageKnife | undefined = ImageKnifeGlobal.getInstance().getImageKnife(); + +@Entry +@Component +struct testImageKnifeCache { + @State url: string = ''; + @State urlGif: string = 'https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658'; + @State urlSvg: string = 'http://1.94.37.200:700/downloadSvgIamge'; + @State urlPng: string = 'https://img-blog.csdnimg.cn/20191215043500229.png'; + @State urlJpg: string = 'https://hbimg.huabanimg.com/cc6af25f8d782d3cf3122bef4e61571378271145735e9-vEVggB'; + @State urlBmp: string = 'https://img-blog.csdn.net/20140514114029140'; + @State urlWebp: string = 'https://hbimg.huabanimg.com/95a6d37a39aa0b70d48fa18dc7df8309e2e0e8e85571e-x4hhks_fw658/format/webp'; + @State imagePixelMap: PixelMap | undefined = undefined; + @State imagePixelMap_: PixelMap | undefined = undefined; + private index_: number = -1; + private tempSize: number = 200; + private comSize: Size = { + width: this.tempSize, + height: this.tempSize, + } + @State imageKnifeOption: ImageKnifeOption = + { + loadSrc: $r('app.media.icon'), + + placeholderSrc: $r('app.media.icon_loading'), + errorholderSrc: $r('app.media.icon_failed') + }; + + hasUrlCache(request: RequestOption) { + let index: number = 0 + let timeId = -1; + imageKnife?.isUrlExist(request).then((data: ImageKnifeData) => { + clearTimeout(timeId); + if (data.isPixelMap()) { + if (data.drawPixelMap) { + let pixelmap = data.drawPixelMap.imagePixelMap + if (pixelmap) { + + if (this.index_ == 1) { + this.imagePixelMap = pixelmap; + } else if (this.index_ == 2) { + this.imagePixelMap_ = pixelmap; + } + } + } + } + if (data.isGIFFrame()) { + if (data.drawGIFFrame) { + if (data.drawGIFFrame.imageGIFFrames) { + let renderGif = () => { + if (data.drawGIFFrame) { + if (data.drawGIFFrame.imageGIFFrames) { + let pixelmap = data.drawGIFFrame.imageGIFFrames[index].drawPixelMap + let delay = data.drawGIFFrame.imageGIFFrames[index].delay + if (pixelmap) { + if (this.index_ == 1) { + this.imagePixelMap = pixelmap; + } else if (this.index_ == 2) { + this.imagePixelMap_ = pixelmap; + } + } + index++; + if (index == data.drawGIFFrame.imageGIFFrames.length - 1) { + index = 0 + } + timeId = setTimeout(renderGif, data!.drawGIFFrame!.imageGIFFrames![index].delay) + } + } + } + renderGif() + } + } + } + }).catch((err: BusinessError) => { + + }); + + } + + build() { + Scroll() { + Column() { + + Text('图片内存和磁盘读取').fontSize(30); + Text('加载的缓存时候关闭掉网络').fontSize(15); + + Row() { + Button('png') + .onClick(() => { + this.index_ = 0; + this.url = this.urlPng; + this.imageKnifeOption = + { + loadSrc: this.url, + placeholderSrc: $r('app.media.icon_loading'), + errorholderSrc: $r('app.media.icon_failed') + } + }) + + Button('bmp') + .onClick(() => { + this.index_ = 0; + this.url = this.urlBmp; + this.imageKnifeOption = + { + loadSrc: this.url, + placeholderSrc: $r('app.media.icon_loading'), + errorholderSrc: $r('app.media.icon_failed') + } + }) + + Button('webp') + .onClick(() => { + this.index_ = 0; + this.url = this.urlWebp; + this.imageKnifeOption = + { + loadSrc: this.url, + placeholderSrc: $r('app.media.icon_loading'), + errorholderSrc: $r('app.media.icon_failed') + } + }) + + Button('jpg') + .onClick(() => { + this.index_ = 0; + this.url = this.urlJpg; + this.imageKnifeOption = + { + loadSrc: this.url, + placeholderSrc: $r('app.media.icon_loading'), + errorholderSrc: $r('app.media.icon_failed') + } + }) + + Button('gif') + .onClick(() => { + this.index_ = 0; + this.url = this.urlGif; + this.imageKnifeOption = + { + loadSrc: this.url, + placeholderSrc: $r('app.media.icon_loading'), + errorholderSrc: $r('app.media.icon_failed') + } + }) + + Button('svg') + .onClick(() => { + this.index_ = 0; + this.url = this.urlSvg; + this.imageKnifeOption = + { + loadSrc: this.url, + placeholderSrc: $r('app.media.icon_loading'), + errorholderSrc: $r('app.media.icon_failed') + } + }) + + } + + + Row() { + Button('缓存图片') + .onClick(() => { + this.index_ = 1; + let request = new RequestOption(); + request.load(this.url) + .setImageViewSize(this.comSize) + .setCacheType(CacheType.Cache); + this.hasUrlCache(request); + + }) + + Button('磁盘图片') + .onClick(() => { + this.index_ = 2; + let request = new RequestOption(); + request.load(this.url) + .setImageViewSize(this.comSize) + .setCacheType(CacheType.Disk); + this.hasUrlCache(request); + + }) + } + + Text('网络图').fontSize(15); + ImageKnifeComponent({ imageKnifeOption: this.imageKnifeOption }) + .width(200) + .height(200) + .backgroundColor(Color.Orange) + Text('缓存图').fontSize(15); + + ImageKnifeComponent({ + imageKnifeOption: { + loadSrc: this.imagePixelMap as image.PixelMap + } + }) + .width(200) + .height(200) + .backgroundColor(Color.Orange) + Text('磁盘图').fontSize(15); + ImageKnifeComponent({ + imageKnifeOption: { + loadSrc: this.imagePixelMap_ as image.PixelMap + } + }) + .width(200) + .height(200) + .backgroundColor(Color.Orange) + + } + .alignItems(HorizontalAlign.Center) + .width('100%') + + } + .width('100%') + .height('100%') + } + + aboutToAppear() { + + } + + aboutToDisappear() { + + } + + onBackPress() { + + } +} \ 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 12657f6..e6ac6e4 100644 --- a/entry/src/main/resources/base/profile/main_pages.json +++ b/entry/src/main/resources/base/profile/main_pages.json @@ -46,6 +46,7 @@ "pages/testImageKnifeAutoWidthPage", "pages/testImageKnifeAutoHeightPage", "pages/testPriorityComponent", - "pages/testReusePhotoPage" + "pages/testReusePhotoPage", + "pages/testImageKnifeCache" ] } \ No newline at end of file diff --git a/library/src/main/ets/components/imageknife/ImageKnife.ets b/library/src/main/ets/components/imageknife/ImageKnife.ets index 30eb5be..76569df 100644 --- a/library/src/main/ets/components/imageknife/ImageKnife.ets +++ b/library/src/main/ets/components/imageknife/ImageKnife.ets @@ -16,7 +16,7 @@ import { DiskLruCache } from "../cache/DiskLruCache" import { EngineKeyFactories } from "../cache/key/EngineKeyFactories" import { EngineKeyInterface } from "../cache/key/EngineKeyInterface" -import { RequestOption, Size } from "../imageknife/RequestOption" +import { CacheType, RequestOption, Size } from "../imageknife/RequestOption" import { AsyncCallback } from "../imageknife/interface/AsyncCallback" import { PlaceHolderManager } from "../imageknife/holder/PlaceHolderManager" import { RetryHolderManager } from "../imageknife/holder/RetryHolderManager" @@ -49,6 +49,10 @@ import { ObjectKey } from './ObjectKey' import { TaskParams } from './TaskParams' import { Constants } from './constants/Constants' import { TransformUtils } from './transform/TransformUtils' +import { GIFParseImpl } from './utils/gif/GIFParseImpl' +import { IParseImage } from './interface/IParseImage' +import { ParseImageUtil } from './utils/ParseImageUtil' +import { SVGParseImpl } from './utils/svg/SVGParseImpl' export class ImageKnife { static readonly SEPARATOR: string = '/' @@ -78,8 +82,10 @@ export class ImageKnife { defaultLifeCycle: IDrawLifeCycle | undefined = undefined; // 开发者可配置全局缓存 engineKeyImpl: EngineKeyInterface; + private mParseImageUtil: IParseImage; private constructor() { + this.mParseImageUtil = new ParseImageUtil(); this.runningMaps = new EasyLinkedHashMap(); this.pendingMaps = new EasyLinkedHashMap(); @@ -322,6 +328,91 @@ export class ImageKnife { return this.parseSource(request); } + + public isUrlExist(request: RequestOption): Promise { + + return new Promise((resolve, reject) => { + this.generateDataCacheKey(request); + let loadComplete = (imageKnifeData: ImageKnifeData) => { + resolve(imageKnifeData); + } + let loadError = (err ?: BusinessError | string) => { + reject(undefined); + } + this.loadMemoryCacheAndDiskFrom(request, loadComplete, loadError); + }) + } + + loadMemoryCacheAndDiskFrom(request: RequestOption, onComplete: (imageKnifeData: ImageKnifeData) => void | PromiseLike, onError: (err?: BusinessError | string) => void) { + + if (request.cacheType == CacheType.Cache) { + + let cache = this.memoryCacheProxy.loadMemoryCache(request.generateCacheKey, true); + if (cache == null || typeof cache == 'undefined') { + onError("No data in cache!") + }else { + cache.waitSaveDisk = false; + //2.网络缓存有数据,返回 + onComplete(cache); + } + + }else if (request.cacheType == CacheType.Disk){ + let cached: ArrayBuffer = DiskLruCache.getFileCacheByFile((ImageKnifeGlobal.getInstance() + .getHapContext() as common.UIAbilityContext).filesDir as string, request.generateDataKey) as ArrayBuffer; + + if (cached != null) { + + let filetype: string | null = this.fileTypeUtil.getFileType(cached); + + if (filetype == null) { + onError('请检查数据源'); + return; + } + + if (!this.fileTypeUtil.isImage(cached)) { + onError('暂不支持的类型!类型=' + filetype); + } + + if (ImageKnifeData.GIF == filetype && !request.dontAnimateFlag) { + let gifParseImpl = new GIFParseImpl() + gifParseImpl.parseGifs(cached, (data?: GIFFrame[], err?: BusinessError | string) => { + if (err) { + onError(err) + } + LogUtil.log("gifProcess data is null:" + (data == null)); + if (!!data) { + let imageKnifeData = ImageKnifeData.createImageGIFFrame(ImageKnifeType.GIFFRAME, data); + LogUtil.log('gifProcess 生成gif 返回数据类型') + onComplete(imageKnifeData) + } else { + onError('Parse GIF callback data is null, you need check callback data!') + } + }) + + } else if (ImageKnifeData.SVG == filetype) { + let svgParseImpl = new SVGParseImpl() + let success = (value: PixelMap) => { + let imageKnifeData = ImageKnifeData.createImagePixelMap(ImageKnifeType.PIXELMAP, value); + onComplete(imageKnifeData); + } + svgParseImpl.parseSvg(request, cached, success, onError) + } else { + //5.磁盘有数据,解析错误返回onError + let success = (value: PixelMap) => { + let imageKnifeData = ImageKnifeData.createImagePixelMap(ImageKnifeType.PIXELMAP, value); + onComplete(imageKnifeData); + } + this.mParseImageUtil.parseImage(cached, success, onError) + } + + } else { + //6.磁盘无数据,返回onError + onError("No data in disk cache!") + } + } + } + + generateDataCacheKey(request: RequestOption) { let factories: EngineKeyInterface; let cacheKey: string; diff --git a/library/src/main/ets/components/imageknife/RequestOption.ets b/library/src/main/ets/components/imageknife/RequestOption.ets index 001d464..b46d2be 100644 --- a/library/src/main/ets/components/imageknife/RequestOption.ets +++ b/library/src/main/ets/components/imageknife/RequestOption.ets @@ -64,14 +64,23 @@ export interface Size { height: number } +export enum CacheType { + //缓存 + Cache, + //磁盘 + Disk +} + export interface DetachFromLayout { detach: () => void } + export enum Priority { HIGH = 0, MEDIUM = 1, LOW = 2 } + export class RequestOption { // 遍历添加图片http请求头 headers: Map = new Map(); @@ -85,6 +94,8 @@ export class RequestOption { addHeaderMap(map: Map) { this.headers = map; } + + cacheType: CacheType = CacheType.Disk; // 优先级 priority: Priority = Priority.MEDIUM; uuid: string = '' // 唯一标识 @@ -129,7 +140,7 @@ export class RequestOption { // 下载原始文件地址 downloadFilePath: string = ""; //磁盘缓存文件路径 - diskMemoryCachePath: string =""; + diskMemoryCachePath: string = ""; // 网络文件下载统一存放 networkCacheFolder: string = "ImageKnifeNetworkFolder" // 主线图片 状态变化 是否加载完成 @@ -164,12 +175,15 @@ export class RequestOption { this.moduleContext = ctx; } } + setPriority(priority: Priority) { this.priority = priority } - setTransformations( array:Array>){ + + setTransformations(array: Array>) { this.transformations = array; } + generateUUID(): string { let d = new Date().getTime(); const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(new RegExp("[xy]", "g"), (c) => { @@ -197,6 +211,11 @@ export class RequestOption { return this; } + setCacheType(cacheType: CacheType) { + this.cacheType = cacheType; + return this; + } + getFilesPath() { return this.filesPath; } @@ -477,7 +496,7 @@ export class RequestOption { } // 占位图解析成功 - placeholderOnComplete = (imageKnifeData:ImageKnifeData) => { + placeholderOnComplete = (imageKnifeData: ImageKnifeData) => { LogUtil.log("placeholderOnComplete has called!"); LogUtil.log("Main Image is Ready:" + this.loadMainReady); if (!this.loadMainReady && !(this.loadErrorReady || this.loadRetryReady) && !this.loadThumbnailReady) { @@ -511,7 +530,7 @@ export class RequestOption { LogUtil.log("缩略图解析失败 error =" + error) } // 加载失败 占位图解析成功 - errorholderOnComplete = (imageKnifeData:ImageKnifeData) => { + errorholderOnComplete = (imageKnifeData: ImageKnifeData) => { // 如果有错误占位图 先解析并保存在RequestOption中 等到加载失败时候进行调用 this.errorholderData = imageKnifeData; if (this.loadErrorReady) { @@ -524,7 +543,7 @@ export class RequestOption { errorholderOnError = (error: BusinessError | string) => { LogUtil.log("失败占位图解析失败 error =" + error) } - retryholderOnComplete = (imageKnifeData:ImageKnifeData) => { + retryholderOnComplete = (imageKnifeData: ImageKnifeData) => { this.retryholderData = imageKnifeData; if (this.loadRetryReady) { if (this.retryholderFunc != undefined) { @@ -535,7 +554,6 @@ export class RequestOption { retryholderOnError = (error: BusinessError | string) => { LogUtil.log("重试占位图解析失败 error =" + error) } - loadComplete = (imageKnifeData: ImageKnifeData) => { this.loadMainReady = true; // 三级缓存数据加载成功 @@ -586,7 +604,6 @@ export class RequestOption { } } } - // 图片文件落盘之后会自动去寻找下一个数据加载 removeCurrentAndSearchNext = () => { if (ImageKnifeGlobal.getInstance().getImageKnife() != undefined) {