diff --git a/library/src/main/ets/components/imageknife/ImageKnife.ets b/library/src/main/ets/components/imageknife/ImageKnife.ets index 4e0ba52..585fd99 100644 --- a/library/src/main/ets/components/imageknife/ImageKnife.ets +++ b/library/src/main/ets/components/imageknife/ImageKnife.ets @@ -53,12 +53,17 @@ import { GIFParseImpl } from './utils/gif/GIFParseImpl' import { IParseImage } from './interface/IParseImage' import { ParseImageUtil } from './utils/ParseImageUtil' import { SVGParseImpl } from './utils/svg/SVGParseImpl' +import { DefaultJobQueue } from './utils/DefaultJobQueue' +import { IJobQueue } from './utils/IJobQueue' +import LightWeightMap from '@ohos.util.LightWeightMap' +import List from '@ohos.util.List'; import { SendableData } from './SendableData' import { collections } from '@kit.ArkTS' import { TaskParams } from './TaskParams' import { MResource } from './utils/MResource' export class ImageKnife { + private TAG:string = "ImageKnife"; static readonly SEPARATOR: string = '/' memoryCache: MemoryLruCache; dataFetch: IDataFetch; @@ -68,9 +73,6 @@ export class ImageKnife { memoryCacheProxy: MemoryCacheProxy = new MemoryCacheProxy(new MemoryLruCache(100, 100 * 1024 * 1024)); headerMap: Map = new Map(); //定义全局map placeholderCache: string = "placeholderCache" - runningMaps: EasyLinkedHashMap; - pendingMaps: EasyLinkedHashMap; - pausedMaps: EasyLinkedHashMap; isPaused: boolean = false; mutex: MethodMutex = new MethodMutex(); fileTypeUtil: FileTypeUtil; // 通用文件格式辨别 @@ -81,6 +83,14 @@ export class ImageKnife { } }; // 全局监听器 + // 排队队列 + private jobQueue: IJobQueue = new DefaultJobQueue() + // 执行中的请求 + private executingJobMap: LightWeightMap> = new LightWeightMap(); + // 最大并发 + private maxRequests: number = 64 + //相同任务等待队列 + pendingJobMap: LightWeightMap> = new LightWeightMap(); // gifWorker gifWorker: worker.ThreadWorker | undefined = undefined; defaultLifeCycle: IDrawLifeCycle | undefined = undefined; @@ -91,10 +101,6 @@ export class ImageKnife { private constructor() { this.mParseImageUtil = new ParseImageUtil(); - this.runningMaps = new EasyLinkedHashMap(); - this.pendingMaps = new EasyLinkedHashMap(); - this.pausedMaps = new EasyLinkedHashMap(); - // 构造方法传入size 为保存文件个数 this.memoryCache = new MemoryLruCache(100, 100 * 1024 * 1024); @@ -226,15 +232,6 @@ export class ImageKnife { // 替代原来的DiskLruCache public replaceDiskLruCache(size: number) { this.diskMemoryCache.setMaxSize(size) - // 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) => { - // newDiskLruCache.set(key, value); - // }) - // this.diskMemoryCache = newDiskLruCache; - // } } // 预加载 resource资源一级缓存,string资源实现二级缓存 @@ -248,28 +245,7 @@ export class ImageKnife { async pauseRequests(): Promise { await this.mutex.lock(async () => { this.isPaused = true; - - // 将未删除的所有request [run pend] 放入 [pause] - LogUtil.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(); - LogUtil.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() - LogUtil.log('dodo pauseRequests start3 pausedMaps size=' + this.pausedMaps.size() + ' runMaps Size=' + this.runningMaps.size() + ' pendMaps Size=' + this.pendingMaps.size()) + LogUtil.log(this.TAG + ' pauseRequests execute') }) } @@ -277,28 +253,15 @@ export class ImageKnife { async resumeRequests(): Promise { await this.mutex.lock(async () => { this.isPaused = false; - LogUtil.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() - LogUtil.log('dodo resumeRequest start1 pausedMaps size=' + this.pausedMaps.size() + ' runMaps Size=' + this.runningMaps.size() + ' pendMaps Size=' + this.pendingMaps.size()) + this.dispatchNextJob(); + LogUtil.log(this.TAG + ' resumeRequests execute') }) } // 删除 请求 remove(uuid: string) { - if (this.isPaused) { - this.pausedMaps.remove(uuid) - } else { // TODO 哪些请求可以删除 - this.pendingMaps.remove(uuid) - this.runningMaps.remove(uuid) - } + this.executingJobMap.remove(uuid) } // 正常加载 @@ -521,112 +484,49 @@ export class ImageKnife { // 删除执行结束的running removeRunning(request: RequestOption) { - if (this.isPaused) { - - } else { - this.runningMaps.remove(request.uuid); - LogUtil.log('dodo runningMaps length =' + this.runningMaps.size()) - let previousRequest = request; - this.loadNextPending(previousRequest); + if (!this.isPaused) { + //不暂停则继续加载 + this.executingJobMap.remove(request.uuid); + LogUtil.log('ImageKnife removeRunning line 534 executingJobMap length =' + this.executingJobMap.length) + this.dispatchNextJob(); } } - // 执行相同key的pending队列请求 - private keyEqualPendingToRun(nextPending: RequestOption) { - - - this.pendingMaps.remove(nextPending.uuid) - this.runningMaps.put(nextPending.uuid, nextPending); - - this.taskpoolLoadResource(nextPending, Constants.MAIN_HOLDER); - - } - - private searchNextKeyToRun() { - // 其次则寻找pending中第一个和running不重复的requestOrKey - 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 (!hasEqual) { - break; - } - pendingTailNode = pendingTailNode.prev; - } - - if (pendingTailNode != null && pendingTailNode.value != null) { - let nextPending = pendingTailNode.value; - this.runningMaps.put(nextPending.uuid, nextPending) - this.pendingMaps.remove(nextPending.uuid) - this.taskpoolLoadResource(nextPending, Constants.MAIN_HOLDER); - } - } - - - // 加载下一个key的请求 - 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 (hasEqualRunning) { - if (tailNode != null && tailNode.value != null) { - this.keyEqualPendingToRun(tailNode.value); - } - } else { - this.searchNextKeyToRun(); + dispatchNextJob() { + let request = this.jobQueue.pop() + if (request !== undefined) { + this.taskpoolLoadResource(request,Constants.MAIN_HOLDER) } } // 启动新线程 去磁盘取 去网络取 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 + if (this.executingJobMap.length > this.maxRequests) { + this.jobQueue.add(request) + return } - - if (hasRunningRequest) { - this.pendingMaps.put(request.uuid, request); - + let requestList: List | undefined = this.executingJobMap.get(request.uuid) + if (requestList == undefined) { + requestList = new List() + requestList.add(request) + this.executingJobMap.set(request.uuid, requestList) + if (!this.isPaused){ + //暂停则不开启加载 + this.taskpoolLoadResource(request, Constants.MAIN_HOLDER); + } } else { - this.runningMaps.put(request.uuid, request) - - this.taskpoolLoadResource(request, Constants.MAIN_HOLDER); + requestList.add(request) + this.executingJobMap.set(request.uuid, requestList) + return } } else { LogUtil.log("key没有生成无法进入存取!") } - } + } private assembleSendableData(request: RequestOption, usageType: string){ @@ -737,7 +637,7 @@ export class ImageKnife { if ((typeof (data as PixelMap).isEditable) == 'boolean') { let imageKnifeData = ImageKnifeData.createImagePixelMap(ImageKnifeType.PIXELMAP, data as PixelMap); request.placeholderOnComplete(imageKnifeData) - this.memoryCacheProxy.putValue(request.placeholderCacheKey,imageKnifeData) + this.memoryCacheProxy.putValue(request.placeholderCacheKey, imageKnifeData) } else { request.placeholderOnError("request placeholder error") } @@ -745,7 +645,7 @@ export class ImageKnife { if ((typeof (data as PixelMap).isEditable) == 'boolean') { let imageKnifeData = ImageKnifeData.createImagePixelMap(ImageKnifeType.PIXELMAP, data as PixelMap); request.retryholderOnComplete(imageKnifeData) - this.memoryCacheProxy.putValue(request.retryholderCacheKey,imageKnifeData) + this.memoryCacheProxy.putValue(request.retryholderCacheKey, imageKnifeData) } else { request.retryholderOnError("request retryholder error") } @@ -753,19 +653,27 @@ export class ImageKnife { if ((typeof (data as PixelMap).isEditable) == 'boolean') { let imageKnifeData = ImageKnifeData.createImagePixelMap(ImageKnifeType.PIXELMAP, data as PixelMap); request.errorholderOnComplete(imageKnifeData) - this.memoryCacheProxy.putValue(request.errorholderCacheKey,imageKnifeData) + this.memoryCacheProxy.putValue(request.errorholderCacheKey, imageKnifeData) } else { request.errorholderOnError("request errorholder error") } } else { if ((typeof (data as PixelMap).isEditable) == 'boolean') { let imageKnifeData = ImageKnifeData.createImagePixelMap(ImageKnifeType.PIXELMAP, data as PixelMap); - request.loadComplete(imageKnifeData) - this.memoryCacheProxy.putValue(request.generateCacheKey,imageKnifeData) + let requestList: List | undefined = this.executingJobMap.get(request.uuid) + requestList.forEach((requestOption:RequestOption)=>{ + requestOption.loadComplete(imageKnifeData) + }) + this.memoryCacheProxy.putValue(request.generateCacheKey, imageKnifeData) + this.setDiskCache(request) } else if ((data as GIFFrame[]).length > 0) { let imageKnifeData = ImageKnifeData.createImageGIFFrame(ImageKnifeType.GIFFRAME, data as GIFFrame[]); - request.loadComplete(imageKnifeData) - this.memoryCacheProxy.putValue(request.generateCacheKey,imageKnifeData) + let requestList: List | undefined = this.executingJobMap.get(request.uuid) + requestList.forEach((requestOption:RequestOption)=>{ + requestOption.loadComplete(imageKnifeData) + }) + this.memoryCacheProxy.putValue(request.generateCacheKey, imageKnifeData) + this.setDiskCache(request) } else { request.loadError("request resources error") } @@ -787,40 +695,6 @@ export class ImageKnife { return false; } - private keyEqual(request1: RequestOption, request2: RequestOption): boolean { - // key 完全相等的情况 - if ( - request1.generateCacheKey == request2.generateCacheKey && - request1.generateResourceKey == request2.generateResourceKey && - request1.generateDataKey == request2.generateDataKey - ) { - return true; - } - - return false; - } - - // 非严格校验模式,如果所有key相等我们认为一定相等, 如果请求类型是string 网络请求url或者uri相等 我们也认为该请求应该只发送一个即可,后续请求会去缓存或者磁盘读取 - private requestOrKeyEqual(request1: RequestOption, request2: RequestOption): boolean { - // key 完全相等的情况 - if ( - request1.generateCacheKey == request2.generateCacheKey && - request1.generateResourceKey == request2.generateResourceKey && - request1.generateDataKey == request2.generateDataKey - ) { - return true; - } - - // 如果加载的是网络url或者是本地文件uri读取,那么loadSrc相同就认为是同一个请求 - if ( - typeof request1.loadSrc == 'string' && typeof request2.loadSrc == 'string' && request1.loadSrc == request2.loadSrc - ) { - return true; - } - - return false; - } - private parseSource(request: RequestOption): void { if ((typeof (request.loadSrc as image.PixelMap).isEditable) == 'boolean') { let imageKnifeData = ImageKnifeData.createImagePixelMap(ImageKnifeType.PIXELMAP, request.loadSrc as PixelMap) @@ -865,6 +739,13 @@ export class ImageKnife { }) } + + setMaxRequests(concurrency: number): void { + if (concurrency > 0) { + this.maxRequests = concurrency + } + } + } diff --git a/library/src/main/ets/components/imageknife/RequestOption.ets b/library/src/main/ets/components/imageknife/RequestOption.ets index 63deff9..6dafe74 100644 --- a/library/src/main/ets/components/imageknife/RequestOption.ets +++ b/library/src/main/ets/components/imageknife/RequestOption.ets @@ -186,14 +186,7 @@ export class RequestOption { this.transformations = array; } generateUUID(): string { - const uuid = util.generateRandomUUID() - // let d = new Date().getTime(); - // const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(new RegExp("[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; + return SparkMD5.hashBinary(JSON.stringify(this.loadSrc)) as string; } setModuleContext(moduleCtx: common.UIAbilityContext) { diff --git a/library/src/main/ets/components/imageknife/utils/DefaultJobQueue.ets b/library/src/main/ets/components/imageknife/utils/DefaultJobQueue.ets new file mode 100644 index 0000000..19f11ee --- /dev/null +++ b/library/src/main/ets/components/imageknife/utils/DefaultJobQueue.ets @@ -0,0 +1,48 @@ +/* + * 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 { IJobQueue } from './IJobQueue' +import Queue from '@ohos.util.Queue'; +import { taskpool } from '@kit.ArkTS'; +import { RequestOption } from '../RequestOption'; + +export class DefaultJobQueue implements IJobQueue { + highQueue: Queue = new Queue(); + normalQueue: Queue = new Queue(); + lowQueue: Queue = new Queue(); + + getQueueLength(): number { + return this.highQueue.length + this.normalQueue.length + this.lowQueue.length + } + + add(request: RequestOption): void { + if (request.priority === undefined || request.priority === taskpool.Priority.MEDIUM) { + this.normalQueue.add(request) + } else if (request.priority === taskpool.Priority.HIGH) { + this.highQueue.add(request) + } else { + this.lowQueue.add(request) + } + } + + pop(): RequestOption | undefined { + if (this.highQueue.length > 0) { + return this.highQueue.pop() + } else if (this.normalQueue.length > 0) { + return this.normalQueue.pop() + } else { + return this.lowQueue.pop() + } + } +} \ No newline at end of file diff --git a/library/src/main/ets/components/imageknife/utils/IJobQueue.ets b/library/src/main/ets/components/imageknife/utils/IJobQueue.ets new file mode 100644 index 0000000..125a51f --- /dev/null +++ b/library/src/main/ets/components/imageknife/utils/IJobQueue.ets @@ -0,0 +1,37 @@ +/* + * 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 { RequestOption } from '../RequestOption' + +export interface IJobQueue { + + /** + * 获取队列长度 + * @returns + */ + getQueueLength(): number + + /** + * 往队列中添加元素 + * @param request + */ + add(request: RequestOption): void + + + /** + * 移除并返回队列头部的元素 + * @returns + */ + pop(): RequestOption | undefined +} \ No newline at end of file