diff --git a/entry/src/main/ets/pages/manyPhotoShowPage.ets b/entry/src/main/ets/pages/manyPhotoShowPage.ets index 1841360..ede54e4 100644 --- a/entry/src/main/ets/pages/manyPhotoShowPage.ets +++ b/entry/src/main/ets/pages/manyPhotoShowPage.ets @@ -31,7 +31,26 @@ struct ManyPhotoShowPage { build() { Column() { + Button('点击暂停加载') + .margin({top:10,bottom:5}) + .onClick(()=>{ + let imageKnife = ImageKnifeGlobal.getInstance().getImageKnife(); + if(imageKnife!= undefined){ + imageKnife.pauseRequests() + } + }) + + Button('点击重新加载') + .margin({top:10,bottom:5}) + .onClick(()=>{ + let imageKnife = ImageKnifeGlobal.getInstance().getImageKnife(); + if(imageKnife!= undefined){ + imageKnife.resumeRequests() + } + }) + Button('设置磁盘存储为50M') + .margin({top:10,bottom:5}) .onClick(()=>{ if(ImageKnifeGlobal.getInstance().getImageKnife() != undefined) { let disk: DiskLruCache | undefined = (ImageKnifeGlobal.getInstance().getImageKnife())?.getDiskMemoryCache(); diff --git a/imageknife/src/main/ets/components/imageknife/ImageKnife.ets b/imageknife/src/main/ets/components/imageknife/ImageKnife.ets index 464efba..85ed51a 100644 --- a/imageknife/src/main/ets/components/imageknife/ImageKnife.ets +++ b/imageknife/src/main/ets/components/imageknife/ImageKnife.ets @@ -15,60 +15,68 @@ import { DiskLruCache } from "@ohos/disklrucache" import { LruCache } from "../cache/LruCache" -import {EngineKeyFactories} from "../cache/key/EngineKeyFactories" -import {EngineKeyInterface} from "../cache/key/EngineKeyInterface" -import {RequestOption} from "../imageknife/RequestOption" -import {AsyncCallback} from "../imageknife/interface/AsyncCallback" -import {PlaceHolderManager} from "../imageknife/holder/PlaceHolderManager" -import {RetryHolderManager} from "../imageknife/holder/RetryHolderManager" -import {ErrorHolderManager} from "../imageknife/holder/ErrorHolderManager" -import {RequestManager} from "../imageknife/requestmanage/RequestManager" -import {NONE} from "../cache/diskstrategy/enum/NONE" -import {FileTypeUtil} from '../imageknife/utils/FileTypeUtil' -import {DownloadClient} from '../imageknife/networkmanage/DownloadClient' -import {IDataFetch} from '../imageknife/networkmanage/IDataFetch' -import {ParseResClient} from '../imageknife/resourcemanage/ParseResClient' -import {IResourceFetch} from '../imageknife/resourcemanage/IResourceFetch' -import {ImageKnifeData,ImageKnifeType} from '../imageknife/ImageKnifeData' -import {ImageKnifeGlobal} from '../imageknife/ImageKnifeGlobal' +import { EngineKeyFactories } from "../cache/key/EngineKeyFactories" +import { EngineKeyInterface } from "../cache/key/EngineKeyInterface" +import { RequestOption } from "../imageknife/RequestOption" +import { AsyncCallback } from "../imageknife/interface/AsyncCallback" +import { PlaceHolderManager } from "../imageknife/holder/PlaceHolderManager" +import { RetryHolderManager } from "../imageknife/holder/RetryHolderManager" +import { ErrorHolderManager } from "../imageknife/holder/ErrorHolderManager" +import { RequestManager } from "../imageknife/requestmanage/RequestManager" +import { NONE } from "../cache/diskstrategy/enum/NONE" +import { FileTypeUtil } from '../imageknife/utils/FileTypeUtil' +import { DownloadClient } from '../imageknife/networkmanage/DownloadClient' +import { IDataFetch } from '../imageknife/networkmanage/IDataFetch' +import { ParseResClient } from '../imageknife/resourcemanage/ParseResClient' +import { IResourceFetch } from '../imageknife/resourcemanage/IResourceFetch' +import { ImageKnifeData, ImageKnifeType } from '../imageknife/ImageKnifeData' +import { ImageKnifeGlobal } from '../imageknife/ImageKnifeGlobal' import image from "@ohos.multimedia.image" -import {CompressBuilder} from "../imageknife/compress/CompressBuilder" +import { CompressBuilder } from "../imageknife/compress/CompressBuilder" import { IDrawLifeCycle } from '../imageknife/interface/IDrawLifeCycle' -import {LogUtil} from '../imageknife/utils/LogUtil' +import { LogUtil } from '../imageknife/utils/LogUtil' +import { EasyLinkedHashMap } from './utils/base/EasyLinkedHashMap' +import { MethodMutex } from './utils/base/MethodMutex' import worker from '@ohos.worker' import common from '@ohos.app.ability.common' +import HashMap from '@ohos.util.HashMap' +import LinkedList from '@ohos.util.LinkedList' export class ImageKnife { static readonly SEPARATOR: string = '/' - - memoryCache: LruCache; - diskMemoryCache: DiskLruCache; - dataFetch: IDataFetch; - resourceFetch: IResourceFetch; - filesPath: string = ""; // data/data/包名/files目录 + memoryCache: LruCache; + diskMemoryCache: DiskLruCache; + dataFetch: IDataFetch; + resourceFetch: IResourceFetch; + filesPath: string = ""; // data/data/包名/files目录 - placeholderCache: string = "placeholderCache" - runningRequest: Array; - pendingRequest: Array; - fileTypeUtil: FileTypeUtil; // 通用文件格式辨别 - diskCacheFolder: string = "ImageKnifeDiskCache" - - - defaultListener: AsyncCallback = { - callback:(err: string, data: ImageKnifeData)=>{return false} + placeholderCache: string = "placeholderCache" + runningMaps: EasyLinkedHashMap; + pendingMaps: EasyLinkedHashMap; + pausedMaps: EasyLinkedHashMap; + isPaused: boolean = false; + mutex: MethodMutex = new MethodMutex(); + fileTypeUtil: FileTypeUtil; // 通用文件格式辨别 + diskCacheFolder: string = "ImageKnifeDiskCache" + defaultListener: AsyncCallback = { + callback: (err: string, data: ImageKnifeData) => { + return false + } }; // 全局监听器 // gifWorker - gifWorker: worker.ThreadWorker|undefined = undefined; - - defaultLifeCycle: IDrawLifeCycle|undefined = undefined; - + gifWorker: worker.ThreadWorker | undefined = undefined; + defaultLifeCycle: IDrawLifeCycle | undefined = undefined; // 开发者可配置全局缓存 - engineKeyImpl: EngineKeyInterface; + engineKeyImpl: EngineKeyInterface; private constructor() { + this.runningMaps = new EasyLinkedHashMap(); + this.pendingMaps = new EasyLinkedHashMap(); + this.pausedMaps = new EasyLinkedHashMap(); + // 构造方法传入size 为保存文件个数 this.memoryCache = new LruCache(100); @@ -84,22 +92,21 @@ export class ImageKnife { // 初始化本地 文件保存 this.filesPath = (ImageKnifeGlobal.getInstance().getHapContext() as common.UIAbilityContext).filesDir as string; - this.runningRequest = new Array(); - this.pendingRequest = new Array(); - // 通用文件格式识别初始化 this.fileTypeUtil = new FileTypeUtil(); this.engineKeyImpl = new EngineKeyFactories(); + + } - getMemoryCache(): LruCache{ + getMemoryCache(): LruCache { return this.memoryCache; } - public static with(context:Object): ImageKnifeGlobal{ + public static with(context: Object): ImageKnifeGlobal { // 存入hapContext; - let global:ImageKnifeGlobal = ImageKnifeGlobal.getInstance(); + let global: ImageKnifeGlobal = ImageKnifeGlobal.getInstance(); global.setHapContext(context) // 初始化ImageKnife @@ -113,7 +120,7 @@ export class ImageKnife { return global; } - getDiskMemoryCache(): DiskLruCache{ + getDiskMemoryCache(): DiskLruCache { return this.diskMemoryCache; }; @@ -121,7 +128,7 @@ export class ImageKnife { this.diskMemoryCache = diskLruCache; }; - getFileTypeUtil(): FileTypeUtil{ + getFileTypeUtil(): FileTypeUtil { return this.fileTypeUtil; } @@ -137,22 +144,23 @@ export class ImageKnife { return this.defaultListener; } - setGifWorker(worker:worker.ThreadWorker){ + setGifWorker(worker: worker.ThreadWorker) { this.gifWorker = worker } - getGifWorker(){ + + getGifWorker() { return this.gifWorker; } - getDefaultLifeCycle(){ + getDefaultLifeCycle() { return this.defaultLifeCycle; } - setDefaultLifeCycle(viewLifeCycle:IDrawLifeCycle){ + setDefaultLifeCycle(viewLifeCycle: IDrawLifeCycle) { this.defaultLifeCycle = viewLifeCycle; } - setEngineKeyImpl(impl:EngineKeyInterface){ + setEngineKeyImpl(impl: EngineKeyInterface) { this.engineKeyImpl = impl; } @@ -162,34 +170,34 @@ export class ImageKnife { this.defaultListener = newDefaultListener; } - public compressBuilder(): CompressBuilder{ + public compressBuilder(): CompressBuilder { return new CompressBuilder(); } // 替代原来的LruCache - public replaceLruCache(size:number){ - if(this.memoryCache.map.size() <= 0) { + public replaceLruCache(size: number) { + if (this.memoryCache.map.size() <= 0) { this.memoryCache = new LruCache(size); - }else{ + } else { let newLruCache = new LruCache(size); - this.memoryCache.foreachLruCache( (value:ImageKnifeData, key:string, map:Object)=> { + this.memoryCache.foreachLruCache((value: ImageKnifeData, key: string, map: Object) => { newLruCache.put(key, value); }) this.memoryCache = newLruCache; } } - public replaceDataFetch(fetch:IDataFetch){ + public replaceDataFetch(fetch: IDataFetch) { this.dataFetch = fetch; } // 替代原来的DiskLruCache - public replaceDiskLruCache(size:number) { + public replaceDiskLruCache(size: number) { if (this.diskMemoryCache.getCacheMap().size() <= 0) { this.diskMemoryCache = DiskLruCache.create(ImageKnifeGlobal.getInstance().getHapContext(), size); } else { let newDiskLruCache = DiskLruCache.create(ImageKnifeGlobal.getInstance().getHapContext(), size); - this.diskMemoryCache.foreachDiskLruCache( (value:string|ArrayBuffer, key:string, map:Object)=> { + this.diskMemoryCache.foreachDiskLruCache((value: string | ArrayBuffer, key: string, map: Object) => { newDiskLruCache.set(key, value); }) this.diskMemoryCache = newDiskLruCache; @@ -197,17 +205,74 @@ export class ImageKnife { } // 预加载 resource资源一级缓存,string资源实现二级缓存 - preload(request: RequestOption):void { + preload(request: RequestOption): void { // 每个request 公共信息补充 request.setFilesPath(this.filesPath); - return this.parseSource(request); } + // 暂停所有请求 + async pauseRequests(): Promise { + await this.mutex.lock(async () => { + this.isPaused = true; + + // 将未删除的所有request [run pend] 放入 [pause] + this.pausedMaps.clear() + console.log('dodo pauseRequests start1 pausedMaps size=' + this.pausedMaps.size() + ' runMaps Size=' + this.runningMaps.size() + ' pendMaps Size=' + this.pendingMaps.size()) + + // 将run存入pause + let runNode = this.runningMaps.getHead(); + while (runNode) { + let request = runNode.value; + this.pausedMaps.put(request.uuid, request) + runNode = runNode.next; + } + this.runningMaps.clear(); + console.log('dodo pauseRequests start2 pausedMaps size=' + this.pausedMaps.size() + ' runMaps Size=' + this.runningMaps.size() + ' pendMaps Size=' + this.pendingMaps.size()) + + let pendNode = this.pendingMaps.getHead(); + while (pendNode) { + let request = pendNode.value; + this.pausedMaps.put(request.uuid, request) + pendNode = pendNode.next + } + this.pendingMaps.clear() + console.log('dodo pauseRequests start3 pausedMaps size=' + this.pausedMaps.size() + ' runMaps Size=' + this.runningMaps.size() + ' pendMaps Size=' + this.pendingMaps.size()) + }) + } + + + async resumeRequests(): Promise { + await this.mutex.lock(async () => { + this.isPaused = false; + console.log('dodo resumeRequest start1 pausedMaps size=' + this.pausedMaps.size() + ' runMaps Size=' + this.runningMaps.size() + ' pendMaps Size=' + this.pendingMaps.size()) + // 重启了之后需要把paused 里面的所有request重新发送 + let headNode = this.pausedMaps.getHead(); + while (headNode) { + let request = headNode.value + this.loadCacheManager(request) + headNode = headNode.next + } + this.pausedMaps.clear() + console.log('dodo resumeRequest start1 pausedMaps size=' + this.pausedMaps.size() + ' runMaps Size=' + this.runningMaps.size() + ' pendMaps Size=' + this.pendingMaps.size()) + }) + } + + // 删除 请求 + remove(uuid: string) { + if (this.isPaused) { + this.pausedMaps.remove(uuid) + } else { + // TODO 哪些请求可以删除 + this.pendingMaps.remove(uuid) + this.runningMaps.remove(uuid) + } + } + // 正常加载 - call(request: RequestOption):void { + call(request: RequestOption): void { // 添加全局监听 - if(this.defaultListener) { + if (this.defaultListener) { request.addListener(this.defaultListener) } @@ -231,28 +296,28 @@ export class ImageKnife { } loadResources(request: RequestOption) { - let factories:EngineKeyInterface; - let cacheKey:string; - let transferKey:string; - let dataKey:string; - if(this.engineKeyImpl){ + let factories: EngineKeyInterface; + let cacheKey: string; + let transferKey: string; + let dataKey: string; + if (this.engineKeyImpl) { factories = this.engineKeyImpl; - }else { + } else { factories = new EngineKeyFactories(); } // 生成内存缓存key 内存 变换后磁盘 let loadKey = ''; - if(typeof request.loadSrc == 'string'){ + if (typeof request.loadSrc == 'string') { loadKey = request.loadSrc; - }else{ + } else { loadKey = JSON.stringify(request.loadSrc); } let size = JSON.stringify(request.size); let transformed = ''; - if(request && request.transformations) { + if (request && request.transformations) { for (let i = 0; i < request.transformations.length; i++) { if (i == request.transformations.length - 1) { transformed += request.transformations[i].getName() + ""; @@ -289,120 +354,133 @@ export class ImageKnife { // 删除执行结束的running removeRunning(request: RequestOption) { - let index = -1; - for (let i = 0; i < this.runningRequest.length; i++) { - let tempRunning = this.runningRequest[i]; - if (this.keyEqual(request, tempRunning)) { - // 如果key相同 说明找到执行的request,我们记录下当前request的index位置 - index = i; - break; - } - } - if (index >= 0) { - let request = this.runningRequest.splice(index, 1)[0]; - this.loadNextPending(request); + if (this.isPaused) { + + } else { + this.runningMaps.remove(request.uuid); + console.log('dodo runningMaps length =' + this.runningMaps.size()) + let previousRequest = request; + this.loadNextPending(previousRequest); } } // 执行相同key的pending队列请求 - keyEqualPendingToRun(index:number){ - let nextPending = this.pendingRequest.splice(index, 1)[0]; - this.runningRequest.push(nextPending) + private keyEqualPendingToRun(nextPending: RequestOption) { + // let nextPending = this.pendingRequest.splice(index, 1)[0]; + // this.runningRequest.push(nextPending) + // RequestManager.execute((nextPending as RequestOption), this.memoryCache, this.diskMemoryCache, this.dataFetch, this.resourceFetch) + + this.pendingMaps.remove(nextPending.uuid) + this.runningMaps.put(nextPending.uuid, nextPending); + RequestManager.execute((nextPending as RequestOption), this.memoryCache, this.diskMemoryCache, this.dataFetch, this.resourceFetch) + } - searchNextKeyToRun(){ + private searchNextKeyToRun() { // 其次则寻找pending中第一个和running不重复的requestOrKey - let index2 = -1; - for (let i = 0; i < this.pendingRequest.length; i++) { - let temppending = this.pendingRequest[i]; - let hasKeyEqual = false; - for (let j = 0; j < this.runningRequest.length; j++) { - let temprunning = this.runningRequest[j]; - if (this.requestOrKeyEqual(temppending, temprunning)) { - hasKeyEqual = true; - break; + let pendingTailNode = this.pendingMaps.getTail(); + while (pendingTailNode) { + let pendingRequest = pendingTailNode.value; + let hasEqual = false; + let runningTailNode = this.runningMaps.getTail(); + while (runningTailNode) { + let runningRequest = runningTailNode.value; + if (this.requestOrKeyEqual(pendingRequest, runningRequest)) { + hasEqual = true; + break } + runningTailNode = runningTailNode.prev; } - if (!hasKeyEqual) { - index2 = i; + + if (hasEqual) { break; } + pendingTailNode = pendingTailNode.prev; } - if (index2 >= 0) { - let nextPending = this.pendingRequest.splice(index2, 1)[0]; - this.runningRequest.push(nextPending) + + if (pendingTailNode != null && pendingTailNode.value != null) { + let nextPending = pendingTailNode.value; + this.runningMaps.put(nextPending.uuid, nextPending) + this.pendingMaps.remove(nextPending.uuid) RequestManager.execute((nextPending as RequestOption), this.memoryCache, this.diskMemoryCache, this.dataFetch, this.resourceFetch) - } else { - // 不执行 } } // 加载下一个key的请求 - loadNextPending(request:RequestOption) { - // 首先寻找被移除key相同的request - let index = -1; - for (let i = 0; i < this.pendingRequest.length; i++) { - let temppending = this.pendingRequest[i]; - if (this.requestOrKeyEqual(request, temppending)) { - // 如果key相同 说明目前有任务正在执行,我们记录下当前request 放入pendingRunning - index = i; + private loadNextPending(request: RequestOption) { + let hasEqualRunning = false; + let tailNode = this.pendingMaps.getTail(); + while (tailNode) { + if (this.requestOrKeyEqual(request, tailNode.value)) { + hasEqualRunning = true; break; } + tailNode = tailNode.prev; } - if (index >= 0) { - this.keyEqualPendingToRun(index); + + if (hasEqualRunning) { + this.keyEqualPendingToRun(tailNode.value); } else { this.searchNextKeyToRun(); } } // 启动新线程 去磁盘取 去网络取 - loadCacheManager(request: RequestOption) { - if (this.keyNotEmpty(request)) { - let hasRunningRequest = false; - for (let i = 0; i < this.runningRequest.length; i++) { - let tempRunning = this.runningRequest[i]; - if (this.requestOrKeyEqual(request, tempRunning)) { + private loadCacheManager(request: RequestOption) { + if (this.isPaused) { + // 将当前request存入pausedMaps + this.pausedMaps.put(request.uuid, request); + } else { + // 正常逻辑 + if (this.keyNotEmpty(request)) { + let hasRunningRequest = false; + // 遍历双向链表 从尾巴到头 + let tailNode = this.runningMaps.getTail(); + while (tailNode) { + if (this.requestOrKeyEqual(request, tailNode.value)) { + hasRunningRequest = true; + break; + } + tailNode = tailNode.prev + } - // 如果requestOrKey相同 说明目前有任务正在执行,我们记录下当前request 放入pendingRunning - hasRunningRequest = true; - break; + if (hasRunningRequest) { + this.pendingMaps.put(request.uuid, request); + + // this.pendingRequest.push(request); + } else { + this.runningMaps.put(request.uuid, request) + + // this.runningRequest.push(request); + // 不存在相同key的 任务可以并行 + RequestManager.execute(request, this.memoryCache, this.diskMemoryCache, this.dataFetch, this.resourceFetch) } } - if (hasRunningRequest) { - this.pendingRequest.push(request); - } else { - this.runningRequest.push(request); - // 不存在相同key的 任务可以并行 - RequestManager.execute(request, this.memoryCache, this.diskMemoryCache, this.dataFetch, this.resourceFetch) + else { + LogUtil.log("key没有生成无法进入存取!") } } - else { - LogUtil.log("key没有生成无法进入存取!") - } - - } - private keyNotEmpty(request: RequestOption): boolean{ + private keyNotEmpty(request: RequestOption): boolean { if ( request.generateCacheKey != null && request.generateCacheKey.length > 0 && - request.generateDataKey != null && request.generateDataKey.length > 0 && - request.generateResourceKey != null && request.generateResourceKey.length > 0 + request.generateDataKey != null && request.generateDataKey.length > 0 && + request.generateResourceKey != null && request.generateResourceKey.length > 0 ) { return true; } return false; } - private keyEqual(request1: RequestOption, request2: RequestOption): boolean{ + private keyEqual(request1: RequestOption, request2: RequestOption): boolean { // key 完全相等的情况 if ( request1.generateCacheKey == request2.generateCacheKey && - request1.generateResourceKey == request2.generateResourceKey && - request1.generateDataKey == request2.generateDataKey + request1.generateResourceKey == request2.generateResourceKey && + request1.generateDataKey == request2.generateDataKey ) { return true; } @@ -411,18 +489,18 @@ export class ImageKnife { } // 非严格校验模式,如果所有key相等我们认为一定相等, 如果请求类型是string 网络请求url或者uri相等 我们也认为该请求应该只发送一个即可,后续请求会去缓存或者磁盘读取 - private requestOrKeyEqual(request1: RequestOption, request2: RequestOption): boolean{ + private requestOrKeyEqual(request1: RequestOption, request2: RequestOption): boolean { // key 完全相等的情况 if ( request1.generateCacheKey == request2.generateCacheKey && - request1.generateResourceKey == request2.generateResourceKey && - request1.generateDataKey == request2.generateDataKey + request1.generateResourceKey == request2.generateResourceKey && + request1.generateDataKey == request2.generateDataKey ) { return true; } // 如果加载的是网络url或者是本地文件uri读取,那么loadSrc相同就认为是同一个请求 - if( + if ( typeof request1.loadSrc == 'string' && typeof request2.loadSrc == 'string' && request1.loadSrc == request2.loadSrc ) { return true; @@ -431,7 +509,7 @@ export class ImageKnife { return false; } - parseSource(request: RequestOption):void { + private parseSource(request: RequestOption): void { if ((typeof (request.loadSrc as image.PixelMap).isEditable) == 'boolean') { let imageKnifeData = ImageKnifeData.createImagePixelMap(ImageKnifeType.PIXELMAP, request.loadSrc as PixelMap) request.loadComplete(imageKnifeData); diff --git a/imageknife/src/main/ets/components/imageknife/ImageKnifeComponent.ets b/imageknife/src/main/ets/components/imageknife/ImageKnifeComponent.ets index ff291ce..6516eb9 100644 --- a/imageknife/src/main/ets/components/imageknife/ImageKnifeComponent.ets +++ b/imageknife/src/main/ets/components/imageknife/ImageKnifeComponent.ets @@ -16,7 +16,7 @@ import { ImageKnifeOption } from '../imageknife/ImageKnifeOption' import { ImageKnifeGlobal } from '../imageknife/ImageKnifeGlobal' import { TransformType } from '../imageknife/transform/TransformType' -import { RequestOption, Size } from '../imageknife/RequestOption' +import { DetachFromLayout, RequestOption, Size } from '../imageknife/RequestOption' import { ImageKnifeData } from '../imageknife/ImageKnifeData' import { GIFFrame } from '../imageknife/utils/gif/GIFFrame' import { IDrawLifeCycle } from '../imageknife/interface/IDrawLifeCycle' @@ -82,6 +82,8 @@ export struct ImageKnifeComponent { private onReadyNext?: (data:ImageKnifeData|number|undefined) => void = undefined private onReadyNextData:ImageKnifeData|number|undefined = undefined + private detachFromLayout:DetachFromLayout; + build() { Canvas(this.context) .width('100%') @@ -275,6 +277,7 @@ export struct ImageKnifeComponent { } this.resetGifData() let request = new RequestOption(); + this.detachFromLayout = request.detachFromLayout; this.configNecessary(request); this.configCacheStrategy(request); this.configDisplay(request); @@ -421,7 +424,7 @@ export struct ImageKnifeComponent { drawPlaceholder(context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) { LogUtil.log('ImageKnifeComponent default drawPlaceholder start!') - + data.drawPixelMap?.imagePixelMap?.getImageInfo().then((imageInfo) => { LogUtil.log('ImageKnifeComponent imageinfo widht =' + imageInfo.size.width + ' height=' + imageInfo.size.height) let scaleType = (typeof imageKnifeOption.placeholderScaleType == 'number') ? imageKnifeOption.placeholderScaleType : ScaleType.FIT_CENTER @@ -476,7 +479,7 @@ export struct ImageKnifeComponent { drawThumbSizeMultiplier(context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) { LogUtil.log('ImageKnifeComponent default drawThumbSizeMultiplier start!') - + data.drawPixelMap?.imagePixelMap?.getImageInfo().then((imageInfo) => { LogUtil.log('ImageKnifeComponent imageinfo widht =' + imageInfo.size.width + ' height=' + imageInfo.size.height) let scaleType = (typeof imageKnifeOption.thumbSizeMultiplierScaleType == 'number') ? imageKnifeOption.thumbSizeMultiplierScaleType : ScaleType.FIT_CENTER @@ -491,7 +494,7 @@ export struct ImageKnifeComponent { drawMainSource(context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) { LogUtil.log('ImageKnifeComponent default drawMainSource start!') if (data.isPixelMap()) { - + data.drawPixelMap?.imagePixelMap?.getImageInfo().then((imageInfo) => { let scaleType = (typeof imageKnifeOption.mainScaleType == 'number') ? imageKnifeOption.mainScaleType : ScaleType.FIT_CENTER LogUtil.log('ImageKnifeComponent imageinfo width =' + imageInfo.size.width + ' height=' + imageInfo.size.height + 'scaleType=' + scaleType) @@ -508,7 +511,7 @@ export struct ImageKnifeComponent { drawRetryholder(context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) { LogUtil.log('ImageKnifeComponent default drawRetryholder start!') - + data.drawPixelMap?.imagePixelMap?.getImageInfo().then((imageInfo) => { LogUtil.log('ImageKnifeComponent imageinfo width =' + imageInfo.size.width + ' height=' + imageInfo.size.height) let scaleType = (typeof imageKnifeOption.retryholderScaleType == 'number') ? imageKnifeOption.retryholderScaleType : ScaleType.FIT_CENTER @@ -522,7 +525,7 @@ export struct ImageKnifeComponent { drawErrorholder(context: CanvasRenderingContext2D, data: ImageKnifeData, imageKnifeOption: ImageKnifeOption, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void) { LogUtil.log('ImageKnifeComponent default drawErrorholder start!') - + data.drawPixelMap?.imagePixelMap?.getImageInfo().then((imageInfo) => { LogUtil.log('ImageKnifeComponent imageinfo widht =' + imageInfo.size.width + ' height=' + imageInfo.size.height) let scaleType = (typeof imageKnifeOption.errorholderSrcScaleType == 'number') ? imageKnifeOption.errorholderSrcScaleType : ScaleType.FIT_CENTER @@ -584,6 +587,10 @@ export struct ImageKnifeComponent { aboutToDisappear() { LogUtil.log('ImageKnifeComponent aboutToDisappear happened!') + if(this.detachFromLayout){ + this.detachFromLayout.detach(); + } + this.resetGifData(); } @@ -601,7 +608,7 @@ export struct ImageKnifeComponent { } private drawLifeCycleHasConsumed( methodName: string, - context: CanvasRenderingContext2D, data: K, imageKnifeOption: T, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void,drawLifeCycle?: IDrawLifeCycle + context: CanvasRenderingContext2D, data: K, imageKnifeOption: T, compWidth: number, compHeight: number, setGifTimeId?: (timeId: number) => void,drawLifeCycle?: IDrawLifeCycle ):boolean { if (drawLifeCycle && (drawLifeCycle as Record)[methodName]) { return (drawLifeCycle as Record)[methodName](context, data, imageKnifeOption, compWidth, compHeight, setGifTimeId) @@ -677,7 +684,7 @@ export struct ImageKnifeComponent { // 理论上该帧在屏幕上保留的时间 let stayTime:number= 0 if(this.renderFrames_frames != undefined) { - stayTime = this.renderFrames_frames[this.renderFrames_index].delay; + stayTime = this.renderFrames_frames[this.renderFrames_index].delay; } if (this.imageKnifeOption.gif && this.imageKnifeOption.gif.speedFactory) { stayTime = stayTime / (this.imageKnifeOption.gif?.speedFactory * 1.0); @@ -706,9 +713,9 @@ export struct ImageKnifeComponent { } private drawFrame(frames: GIFFrame[]|undefined, index: number, context: CanvasRenderingContext2D|undefined, compWidth: number, compHeight: number) { - if(frames == undefined){ - return - } + if(frames == undefined){ + return + } // get current frame let frame = frames[index]; if (!frame || !context) { @@ -848,9 +855,9 @@ export class ScaleTypeHelper { let dy:number = (compHeight - imageHeight * minScale) / (minScale * 1.0); let dw:number = imageWidth; let dh:number = imageHeight; - if(source!= undefined) { - context.drawImage(source, dx + (imageOffsetX != undefined ? imageOffsetX : 0), dy + (imageOffsetY != undefined ? imageOffsetY : 0)) - } + if(source!= undefined) { + context.drawImage(source, dx + (imageOffsetX != undefined ? imageOffsetX : 0), dy + (imageOffsetY != undefined ? imageOffsetY : 0)) + } } static drawFitCenter(context: CanvasRenderingContext2D, source: PixelMap | undefined, minScale: number, imageWidth: number, imageHeight: number, compWidth: number, compHeight: number, imageOffsetX?: number, imageOffsetY?: number) { @@ -859,9 +866,9 @@ export class ScaleTypeHelper { let dy:number = (compHeight - imageHeight * minScale) / (minScale * 2.0); let dw:number = imageWidth; let dh:number = imageHeight; - if(source!= undefined) { - context.drawImage(source, dx + (imageOffsetX != undefined ? imageOffsetX : 0), dy + (imageOffsetY != undefined ? imageOffsetY : 0)) - } + if(source!= undefined) { + context.drawImage(source, dx + (imageOffsetX != undefined ? imageOffsetX : 0), dy + (imageOffsetY != undefined ? imageOffsetY : 0)) + } } static drawCenter(context: CanvasRenderingContext2D, source: PixelMap | undefined, imageWidth: number, imageHeight: number, compWidth: number, compHeight: number, imageOffsetX?: number, imageOffsetY?: number) { @@ -869,9 +876,9 @@ export class ScaleTypeHelper { let dy:number = (compHeight - imageHeight) / 2.0; let dw:number = imageWidth; let dh:number = imageHeight; - if(source!= undefined) { - context.drawImage(source, dx + (imageOffsetX != undefined ? imageOffsetX : 0), dy + (imageOffsetY != undefined ? imageOffsetY : 0)) - } + if(source!= undefined) { + context.drawImage(source, dx + (imageOffsetX != undefined ? imageOffsetX : 0), dy + (imageOffsetY != undefined ? imageOffsetY : 0)) + } } static drawCenterCrop(context: CanvasRenderingContext2D, source: PixelMap | undefined, maxScale: number, imageWidth: number, imageHeight: number, compWidth: number, compHeight: number, imageOffsetX?: number, imageOffsetY?: number) { @@ -880,9 +887,9 @@ export class ScaleTypeHelper { let dy:number = (compHeight - imageHeight * maxScale) / (maxScale * 2.0); let dw:number = imageWidth; let dh:number = imageHeight; - if(source!= undefined) { - context.drawImage(source, dx + (imageOffsetX != undefined ? imageOffsetX : 0), dy + (imageOffsetY != undefined ? imageOffsetY : 0)) - } + if(source!= undefined) { + context.drawImage(source, dx + (imageOffsetX != undefined ? imageOffsetX : 0), dy + (imageOffsetY != undefined ? imageOffsetY : 0)) + } } static drawFitXY(context: CanvasRenderingContext2D, source: PixelMap | undefined, scaleW: number, scaleH: number, imageWidth: number, imageHeight: number, imageOffsetX?: number, imageOffsetY?: number) { @@ -891,9 +898,9 @@ export class ScaleTypeHelper { let dy:number = 0; let dw:number = imageWidth; let dh:number = imageHeight; - if(source!= undefined) { - context.drawImage(source, dx + (imageOffsetX != undefined ? imageOffsetX : 0), dy + (imageOffsetY != undefined ? imageOffsetY : 0)) - } + if(source!= undefined) { + context.drawImage(source, dx + (imageOffsetX != undefined ? imageOffsetX : 0), dy + (imageOffsetY != undefined ? imageOffsetY : 0)) + } } static drawCenterInside(context: CanvasRenderingContext2D, source: PixelMap | undefined, minScale: number, imageWidth: number, imageHeight: number, compWidth: number, compHeight: number, imageOffsetX?: number, imageOffsetY?: number) { @@ -912,7 +919,7 @@ export class ScaleTypeHelper { if(source!= undefined) { context.drawImage(source, dx + (imageOffsetX != undefined ? imageOffsetX : 0), dy + (imageOffsetY != undefined ? imageOffsetY : 0)) } - } + } } diff --git a/imageknife/src/main/ets/components/imageknife/RequestOption.ets b/imageknife/src/main/ets/components/imageknife/RequestOption.ets index 14665ef..5daad2b 100644 --- a/imageknife/src/main/ets/components/imageknife/RequestOption.ets +++ b/imageknife/src/main/ets/components/imageknife/RequestOption.ets @@ -54,7 +54,12 @@ export interface Size { width: number, height: number } +export interface DetachFromLayout{ + detach:()=>void +} + export class RequestOption { + uuid:string ='' // 唯一标识 loadSrc: string | PixelMap | Resource = ''; strategy: DiskStrategy = new AUTOMATIC(); dontAnimateFlag = false; @@ -117,11 +122,30 @@ export class RequestOption { // 缩略图展示 loadThumbnailReady = false; - + detachFromLayout:DetachFromLayout = { + detach: ()=>{ + let imageKnife:ImageKnife | undefined = ImageKnifeGlobal.getInstance().getImageKnife(); + if(imageKnife != undefined) { + imageKnife.remove(this.uuid); + } + } + } constructor() { // 初始化全局监听 this.requestListeners = new Array(); + // 初始化唯一标识,可以用这个标识找到ImageKnife中的对象 + this.uuid = this.generateUUID(); + } + + generateUUID(): string { + let d = new Date().getTime(); + const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { + const r = (d + Math.random() * 16) % 16 | 0; + d = Math.floor(d / 16); + return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); + }); + return uuid; } /** @@ -149,8 +173,6 @@ export class RequestOption { this.cachesPath = path; } - - load(src: string | PixelMap | Resource) { this.loadSrc = src; return this; @@ -489,15 +511,15 @@ export class RequestOption { // 非落盘情况,直接进行寻找下一个加载 let imageKnife:ImageKnife | undefined = ImageKnifeGlobal.getInstance().getImageKnife(); if(imageKnife != undefined) { - imageKnife.removeRunning(this); + imageKnife.removeRunning(this); } } - // 加载成功之后 - let imageKnife:ImageKnife | undefined = ImageKnifeGlobal.getInstance().getImageKnife(); - if(imageKnife != undefined) { - imageKnife.removeRunning(this); - } + // // 加载成功之后 + // let imageKnife:ImageKnife | undefined = ImageKnifeGlobal.getInstance().getImageKnife(); + // if(imageKnife != undefined) { + // imageKnife.removeRunning(this); + // } } // 图片文件落盘之后会自动去寻找下一个数据加载 diff --git a/imageknife/src/main/ets/components/imageknife/networkmanage/DownloadClient.ets b/imageknife/src/main/ets/components/imageknife/networkmanage/DownloadClient.ets index 820073b..1615075 100644 --- a/imageknife/src/main/ets/components/imageknife/networkmanage/DownloadClient.ets +++ b/imageknife/src/main/ets/components/imageknife/networkmanage/DownloadClient.ets @@ -15,7 +15,8 @@ import { IDataFetch } from '../networkmanage/IDataFetch' import { RequestOption } from '../RequestOption' -import { NetworkDownloadClient } from './NetworkDownloadClient' + +import { HttpDownloadClient } from './HttpDownloadClient' import { LoadLocalFileClient } from './LoadLocalFileClient' import { LoadDataShareFileClient } from './LoadDataShareFileClient' import loadRequest from '@ohos.request'; @@ -24,7 +25,7 @@ import common from '@ohos.app.ability.common' // 数据加载器 export class DownloadClient implements IDataFetch { - private networkClient = new NetworkDownloadClient(); + private httpDownloadClient = new HttpDownloadClient(); private localFileClient = new LoadLocalFileClient(); private dataShareFileClient = new LoadDataShareFileClient(); @@ -41,7 +42,7 @@ export class DownloadClient implements IDataFetch { this.dataShareFileClient.loadData(request, onCompleteFunction, onErrorFunction) } else { // 网络下载 - this.networkClient.loadData(request, onCompleteFunction, onErrorFunction) + this.httpDownloadClient.loadData(request, onCompleteFunction, onErrorFunction) } } } diff --git a/imageknife/src/main/ets/components/imageknife/networkmanage/HttpDownloadClient.ets b/imageknife/src/main/ets/components/imageknife/networkmanage/HttpDownloadClient.ets new file mode 100644 index 0000000..0551f72 --- /dev/null +++ b/imageknife/src/main/ets/components/imageknife/networkmanage/HttpDownloadClient.ets @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2021 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 { IDataFetch } from '../networkmanage/IDataFetch' +import { RequestOption } from '../RequestOption' +import { SparkMD5 } from '../../3rd_party/sparkmd5/spark-md5' +import { FileUtils } from '../../cache/FileUtils' +import loadRequest from '@ohos.request'; +import { LogUtil } from '../utils/LogUtil' +import { ImageKnifeGlobal } from '../ImageKnifeGlobal' +import common from '@ohos.app.ability.common' +import { BusinessError } from '@ohos.base' +import http from '@ohos.net.http' +// 数据加载器 +class RequestData{ + receiveSize: number = 2000 + totalSize: number = 2000 +} + +export class HttpDownloadClient implements IDataFetch { + loadData(request: RequestOption, onComplete: (img: ArrayBuffer) => void, onError: (err: string) => void) { + try { + let httpRequest = http.createHttp() + let arrayBuffers = new Array(); + httpRequest.on('headersReceive', (header: Object) => { + // 跟服务器连接成功准备下载 + if (request.progressFunc) { + // 进度条为0 + request.progressFunc.asyncSuccess(0) + } + }) + httpRequest.on('dataReceive', (data: ArrayBuffer) => { + // 下载数据流多次返回 + arrayBuffers.push(data); + }) + + httpRequest.on('dataReceiveProgress', (data: RequestData) => { + // 下载进度 + let percent = Math.round(((data.receiveSize * 1.0) / (data.totalSize * 1.0)) * 100) + if (request.progressFunc) { + request.progressFunc.asyncSuccess(percent) + } + }) + + httpRequest.on('dataEnd', () => { + // 下载完毕 + let combineArray = this.combineArrayBuffers(arrayBuffers); + onComplete(combineArray) + }) + + httpRequest.requestInStream( + request.loadSrc as string, + { + method: http.RequestMethod.GET, + expectDataType: http.HttpDataType.ARRAY_BUFFER, + connectTimeout: 60000, // 可选 默认60000ms + readTimeout: 0, //可选, 默认为60000ms + usingProtocol: http.HttpProtocol.HTTP1_1, // 可选,协议类型默认值由系统自动指定 + }, + (err: BusinessError, data: number) => { + if (!err && data == 200) { + + } else { + httpRequest.off('headersReceive'); + httpRequest.off('dataReceive'); + httpRequest.off('dataReceiveProgress'); + httpRequest.off('dataEnd'); + httpRequest.destroy() + onError('HttpDownloadClient err message =' + err.message) + } + } + ) + } catch (err) { + onError('HttpDownloadClient catch err request uuid ='+request.uuid) + } + } + + combineArrayBuffers(arrayBuffers: ArrayBuffer[]): ArrayBuffer { + // 计算多个ArrayBuffer的总字节大小 + let totalByteLength = 0; + for (const arrayBuffer of arrayBuffers) { + totalByteLength += arrayBuffer.byteLength; + } + + // 创建一个新的ArrayBuffer + const combinedArrayBuffer = new ArrayBuffer(totalByteLength); + + // 创建一个Uint8Array来操作新的ArrayBuffer + const combinedUint8Array = new Uint8Array(combinedArrayBuffer); + + // 依次复制每个ArrayBuffer的内容到新的ArrayBuffer中 + let offset = 0; + for (const arrayBuffer of arrayBuffers) { + const sourceUint8Array = new Uint8Array(arrayBuffer); + combinedUint8Array.set(sourceUint8Array, offset); + offset += sourceUint8Array.length; + } + + return combinedArrayBuffer; + } +} \ No newline at end of file diff --git a/imageknife/src/main/ets/components/imageknife/requestmanage/RequestManager.ets b/imageknife/src/main/ets/components/imageknife/requestmanage/RequestManager.ets index ac20bca..33b41d5 100644 --- a/imageknife/src/main/ets/components/imageknife/requestmanage/RequestManager.ets +++ b/imageknife/src/main/ets/components/imageknife/requestmanage/RequestManager.ets @@ -292,6 +292,8 @@ export class RequestManager { // 处理磁盘加载 网络加载 this.runWrapped(this.options, this.mRunReason, onComplete, onError) } else { + // 需要清理状态 + cache.waitSaveDisk = false; onComplete(cache); return diff --git a/imageknife/src/main/ets/components/imageknife/utils/FileTypeUtil.ets b/imageknife/src/main/ets/components/imageknife/utils/FileTypeUtil.ets index 21e28c7..51ca851 100644 --- a/imageknife/src/main/ets/components/imageknife/utils/FileTypeUtil.ets +++ b/imageknife/src/main/ets/components/imageknife/utils/FileTypeUtil.ets @@ -12,143 +12,80 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import {LogUtil} from '../../imageknife/utils/LogUtil' +import { LogUtil } from '../../imageknife/utils/LogUtil' + +// jpg = 'jpg,0,FFD8', +// png = 'png,0,89504E470D0A1A0A', +// bmp = 'bmp,0,424D', +// gif = 'gif,0,474946383961', +// svg = 'svg,0,3C3F786D6C', +// webp = 'webp,0,52494646', +// tiff = 'tiff,0,492049|49492A00|4D4D002A|4D4D002B' + export class FileTypeUtil { - private map:Map = new Map(); - private READ_MIN_LENGTH:number = 0; - private SUPPORT_FORMATS = [ - PhotoFormat.jpg, - PhotoFormat.png, - PhotoFormat.tiff, - PhotoFormat.bmp, - PhotoFormat.webp, - PhotoFormat.svg, - PhotoFormat.gif - ] + private fileSignatureMap: Record> = { + // 添加文件类型和对应的文件头部特征 + 'jpg': [new Uint8Array([0xFF, 0xD8])], + 'png': [new Uint8Array([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A])], + 'gif': [new Uint8Array([0x47, 0x49, 0x46, 0x38, 0x39, 0x61])], + 'bmp': [new Uint8Array([0x42, 0x4D])], + 'svg': [new Uint8Array([0x3C, 0x3F, 0x78, 0x6D, 0x6C])], + 'webp': [new Uint8Array([0x52, 0x49, 0x46, 0x46])], + 'tiff': [new Uint8Array([0x49, 0x20, 0x49]), new Uint8Array([0x49, 0x49, 0x2A, 0x00]), new Uint8Array([0x4D, 0x4D, 0x00, 0x2A]), new Uint8Array([0x4D, 0x4D, 0x00, 0x2B])], + // 添加更多的文件类型和特征 + }; + constructor() { - this.initImageType(); - } - private getDataViewAt(dataView:DataView,index:number): string{ - return this.dec2Hex(dataView.getUint8(index)) - } - - - - getFileType(arraybuffer: ArrayBuffer):string { - let fileType:string = ''; - if (arraybuffer == null || arraybuffer == undefined || arraybuffer.byteLength <= this.READ_MIN_LENGTH) { - return ''; - } - - let dataView = new DataView(arraybuffer); - LogUtil.log('dataView +'+this.getDataViewAt(dataView,0)+this.getDataViewAt(dataView,1)) - let entries: IterableIterator = this.map.entries(); - for (let i = 0; i < this.map.size; i++) { - let entry:Object[] = entries.next().value; - let key = entry[0] as string - let value = entry[1] as string - let keySplit = key.split(',') - if(keySplit.length == 2){ - let offset = Number(keySplit[0]) - let magicStringLength = keySplit[1].length; - let readLength = magicStringLength/2; - let start = 0; - let fileMagic = '' - while(start< readLength){ - fileMagic+=this.getDataViewAt(dataView,offset+start) - start++; - } - if(fileMagic == keySplit[1]){ - LogUtil.log('匹配到了 fileType='+value) - fileType = value - break; - } - } - } - return fileType; - } - - dec2Hex(uint8: number) { - let hex = uint8.toString(16); - if (hex.length <= 1) { - hex = "0" + hex; - } - return hex.toUpperCase(); - } - - private initImageType() { - if(!this.map){ - this.map = new Map() - } - this.SUPPORT_FORMATS.forEach((value,index,arrs)=>{ - let values = value.split(',') - if(values.length == 3){ - let magicSplits = values[2].split("|") - if(magicSplits.length == 1){ - this.map.set(values[1]+','+values[2],values[0]) - }else if(magicSplits.length > 1){ - for(let i=0; i = this.map.entries(); - for (let i = 0; i < this.map.size; i++) { - let entry:Object[] = entries.next().value; - let key = entry[0] as string - let value = entry[1] as string - LogUtil.log('key='+key+'---value='+value) - } - } - - private initReadMinLength(){ - let max = 0; - this.map.forEach((value,key,map)=>{ - let keySplit = key.split(','); - if(keySplit.length == 2){ - let offset = Number(keySplit[0]) - let magicStringLength = keySplit[1].length; - let tempMax = offset + magicStringLength/2; - if(tempMax > max){ - max = tempMax; - } - } - }) - this.READ_MIN_LENGTH =max; - } - - isImage(arraybuffer:ArrayBuffer){ + isImage(arraybuffer: ArrayBuffer) { let value = this.getFileType(arraybuffer); - if( + if ( value == SupportFormat.jpg || - value == SupportFormat.png || - value == SupportFormat.tiff || - value == SupportFormat.webp || - value == SupportFormat.bmp || - value == SupportFormat.gif || - value == SupportFormat.svg - ){ + value == SupportFormat.png || + value == SupportFormat.tiff || + value == SupportFormat.webp || + value == SupportFormat.bmp || + value == SupportFormat.gif || + value == SupportFormat.svg + ) { return true; } return false; } + getFileType(file: ArrayBuffer): string | null { + const fileData = new Uint8Array(file); + for (const fileType in this.fileSignatureMap) { + const bufferList = this.fileSignatureMap[fileType]; + for (let i = 0; i < bufferList.length; i++) { + let signature = bufferList[i]; + if(this.matchesSignature(fileData,signature)){ + return fileType; + } + } + } + + return null; // 若无法识别文件类型,返回null + } + + + matchesSignature(fileData: Uint8Array, signature: Uint8Array): boolean { + if (fileData.length < signature.length) { + return false; // 文件长度不足,无法匹配魔数 + } + + for (let i = 0; i < signature.length; i++) { + if (fileData[i] !== signature[i]) { + return false; // 魔数不匹配 + } + } + return true; // 文件头部魔数匹配 + } } + export enum PhotoFormat { jpg = 'jpg,0,FFD8', png = 'png,0,89504E470D0A1A0A', diff --git a/imageknife/src/main/ets/components/imageknife/utils/base/EasyLinkedHashMap.ets b/imageknife/src/main/ets/components/imageknife/utils/base/EasyLinkedHashMap.ets new file mode 100644 index 0000000..7f19340 --- /dev/null +++ b/imageknife/src/main/ets/components/imageknife/utils/base/EasyLinkedHashMap.ets @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2023 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. + */ + + +class Node { + key: K; + value: V; + next: Node | null; + prev: Node | null; + + constructor(key: K, value: V) { + this.key = key; + this.value = value; + this.next = null; + this.prev = null; + } +} +// 链表的tail是最近访问,head是最晚访问,对顺序有要求需要访问tail +export class EasyLinkedHashMap { + private map: Map>; // 存储键值对的哈希映射 + private head: Node | null; // 链表头节点 + private tail: Node | null; // 链表尾节点 + + constructor() { + this.map = new Map>(); + this.head = null; + this.tail = null; + } + + // 添加键值对到映射中,并在链表尾部添加新节点 + put(key: K, value: V) { + if (this.map.has(key)) { + // 如果键已存在,则更新对应的值 + const node = this.map.get(key)!; + node.value = value; + this.moveToTail(node); + } else { + // 创建新节点并加入链表尾部 + const newNode = new Node(key, value); + this.map.set(key, newNode); + if (this.tail) { + this.tail.next = newNode; + newNode.prev = this.tail; + this.tail = newNode; + } else { + this.head = newNode; + this.tail = newNode; + } + } + } + + // 从映射中移除键值对,并从链表中删除对应节点 + remove(key: K) { + if (this.map.has(key)) { + const node = this.map.get(key)!; + this.map.delete(key); + this.removeNode(node); + } + } + + // 获取键对应的值,并将对应节点移到链表尾部(表示最近访问) + get(key: K): V | undefined { + if (this.map.has(key)) { + const node = this.map.get(key)!; + this.moveToTail(node); + return node.value; + } + return undefined; + } + + // 将节点移动到链表尾部 + private moveToTail(node: Node) { + if (node === this.tail) { + return; // 节点已在链表尾部,无需移动 + } + + if (node === this.head) { + this.head = node.next; + } else { + node.prev!.next = node.next; + } + + node.next!.prev = node.prev; + this.tail!.next = node; + node.prev = this.tail; + node.next = null; + this.tail = node; + } + + // 从链表中删除节点 + private removeNode(node: Node) { + if (node === this.head) { + this.head = node.next; + } else { + node.prev!.next = node.next; + } + + if (node === this.tail) { + this.tail = node.prev; + } else { + node.next!.prev = node.prev; + } + } + + // 返回映射中的键值对数量 + size(): number { + return this.map.size; + } + // 返回顺序双向链表 + getTail(){ + return this.tail; + } + + getHead(){ + return this.head; + } + clear(){ + this.map.clear() + this.head = null; + this.tail=null; + } +} + +function testLinkedHashMap(){ + // 创建一个新的 LinkedHashMap 实例 + let linkedHashMap = new EasyLinkedHashMap(); + + // 添加键值对,并验证插入是否正确 + linkedHashMap.put('key1', 1); + linkedHashMap.put('key2', 2); + linkedHashMap.put('key3', 3); + + console.log('dodo '+linkedHashMap.get('key1')); // 1 + console.log('dodo '+linkedHashMap.get('key2')); // 2 + console.log('dodo '+linkedHashMap.get('key3')); // 3 + + // 验证键值对更新是否正确 + linkedHashMap.put('key2', 20); + console.log('dodo '+linkedHashMap.get('key2')); // 20 + + // 验证移除键值对是否正确 + linkedHashMap.remove('key1'); + console.log('dodo '+linkedHashMap.get('key1')); // undefined + console.log('dodo '+linkedHashMap.size()); // 2 + + // 添加更多键值对 + linkedHashMap.put('key4', 4); + linkedHashMap.put('key5', 5); + linkedHashMap.put('key6', 6); + + console.log('dodo '+linkedHashMap.size()); // 5 + + // 验证访问顺序是否正确,最近访问的键值对应该在链表尾部 + console.log('dodo '+linkedHashMap.get('key2')); // 20 + console.log('dodo '+linkedHashMap.get('key3')); // 3 + console.log('dodo '+linkedHashMap.get('key4')); // 4 + + // 添加更多键值对,超出容量限制(假设容量限制为 5) + linkedHashMap.put('key7', 7); + linkedHashMap.put('key8', 8); + linkedHashMap.put('key9', 9); + + console.log('dodo '+linkedHashMap.size()); // 5 + console.log('dodo '+linkedHashMap.get('key1')); // undefined,因为已经超过容量限制,最早访问的键值对被移除 + + // 验证移除最近访问的键值对后,访问顺序是否正确 + linkedHashMap.remove('key4'); + console.log('dodo '+linkedHashMap.get('key4')); // undefined + console.log('dodo '+linkedHashMap.get('key2')); // 20 + console.log('dodo '+linkedHashMap.get('key3')); // 3 + + // 清空 LinkedHashMap + linkedHashMap.remove('key2'); + linkedHashMap.remove('key3'); + linkedHashMap.remove('key5'); + linkedHashMap.remove('key6'); + linkedHashMap.remove('key7'); + linkedHashMap.remove('key8'); + linkedHashMap.remove('key9'); + + console.log('dodo '+linkedHashMap.size()); // 0 +} \ No newline at end of file diff --git a/imageknife/src/main/ets/components/imageknife/utils/base/MethodMutex.ets b/imageknife/src/main/ets/components/imageknife/utils/base/MethodMutex.ets new file mode 100644 index 0000000..e3165d8 --- /dev/null +++ b/imageknife/src/main/ets/components/imageknife/utils/base/MethodMutex.ets @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2023 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. + */ + +export class MethodMutex { + private mutex: Promise; + private release: () => void; + + constructor() { + this.mutex = Promise.resolve(); + this.release = () => {}; + } + + async lock(fn: () => Promise): Promise { + await this.mutex; + let result: T; + + try { + this.mutex = new Promise((resolve) => { + this.release = resolve; + }); + result = await fn(); + } finally { + this.release(); + } + + return result; + } +} \ No newline at end of file