From 87f3dab4fa7d3e64610b9fb3043e6c7e8c6d73f9 Mon Sep 17 00:00:00 2001 From: chongtiantian Date: Wed, 24 Apr 2024 10:05:23 +0800 Subject: [PATCH] =?UTF-8?q?ImageKnife=E6=8E=A7=E5=88=B6=E9=87=8D=E8=A6=81?= =?UTF-8?q?=E5=9B=BE=E7=89=87=E8=AF=B7=E6=B1=82=E5=8A=A0=E8=BD=BD=E4=BC=98?= =?UTF-8?q?=E5=85=88=E7=BA=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: chongtiantian --- CHANGELOG.md | 1 + README.md | 22 +- .../main/ets/pages/testPriorityComponent.ets | 236 +++++++++++----- .../ets/test/DefaultJobQueueTest.test.ets | 64 +++++ entry/src/ohosTest/ets/test/List.test.ets | 2 + .../ets/components/imageknife/ImageKnife.ets | 260 +++++------------- .../components/imageknife/RequestOption.ets | 14 +- .../imageknife/utils/DefaultJobQueue.ets | 48 ++++ .../components/imageknife/utils/IJobQueue.ets | 36 +++ 9 files changed, 410 insertions(+), 273 deletions(-) create mode 100644 entry/src/ohosTest/ets/test/DefaultJobQueueTest.test.ets create mode 100644 library/src/main/ets/components/imageknife/utils/DefaultJobQueue.ets create mode 100644 library/src/main/ets/components/imageknife/utils/IJobQueue.ets diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dbda67..bab7ab2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - ImageKnife支持根据自定义key获取已缓存的图片 - ImageKnife加载图片支持自定义网络栈和图片加载组件 - 适配复用场景触发懒加载onDataReloaded +- ImageKnife控制重要图片请求加载优先级 ## 2.2.0-rc.0 - 修复自定义DataFetch接口实现不生效问题 diff --git a/README.md b/README.md index dbe0121..5cd4314 100644 --- a/README.md +++ b/README.md @@ -337,13 +337,14 @@ request.skipMemoryCache(true) ### ImageKnife 启动器/门面类 -| 方法名 | 入参 | 接口描述 | -| ------------------------------- | ---------------------- | ---------------------------------- | -| call(request: RequestOption) | request: RequestOption | 根据用户配置参数具体执行加载流程 | -| preload(request: RequestOption) | request: RequestOption | 根据用户配置参数具体执行预加载流程 | -| pauseRequests() | | 全局暂停请求 | -| resumeRequests() | | 全局恢复暂停 | -| isUrlExist(url, cacheType, size)| url, CacheType, Size | 判断图片是否在 缓存和磁盘中存在,如果入参是缓存,需要传入值图片大小,参数 CacheType, Size(可选)| +| 方法名 | 入参 | 接口描述 | +|----------------------------------| ---------------------- | ------------------------------------------------------------ | +| call(request: RequestOption) | request: RequestOption | 根据用户配置参数具体执行加载流程 | +| preload(request: RequestOption) | request: RequestOption | 根据用户配置参数具体执行预加载流程 | +| pauseRequests() | | 全局暂停请求 | +| resumeRequests() | | 全局恢复暂停 | +| isUrlExist(url, cacheType, size) | url, CacheType, Size | 判断图片是否在 缓存和磁盘中存在,如果入参是缓存,需要传入值图片大小,参数 CacheType, Size(可选) | +| setMaxRequests(count: number) | count | 设置请求的最大并发数量 | ### 缓存策略相关 @@ -448,6 +449,13 @@ export default class EntryAbility extends UIAbility { } ``` +### Queue + +| 方法名 | 入参 | 接口描述 | +| ----------------------------------- | ----------------------- | ------------------------------ | +| getQueueLength(): number | | 获取队列总长度 | +| add(request: RequestOption) | request:RequestOption | 在队列尾部插入元素 | +| pop(): RequestOption | undefined | | 删除队列头元素并返回该删除元素 | ## 约束与限制 diff --git a/entry/src/main/ets/pages/testPriorityComponent.ets b/entry/src/main/ets/pages/testPriorityComponent.ets index 5630f3c..e496e8c 100644 --- a/entry/src/main/ets/pages/testPriorityComponent.ets +++ b/entry/src/main/ets/pages/testPriorityComponent.ets @@ -12,83 +12,177 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ImageKnifeComponent , Priority , NONE } from '@ohos/libraryimageknife' +import { ImageKnifeComponent, ImageKnifeOption, Priority, NONE, ImageKnifeGlobal } from '@ohos/libraryimageknife'; +const dataBak: ImageKnifeOption[] = [ + { + loadSrc: "http://b.hiphotos.baidu.com/image/pic/item/9d82d158ccbf6c81b94575cfb93eb13533fa40a2.jpg", + priority: Priority.HIGH + }, + { + loadSrc: "http://e.hiphotos.baidu.com/image/pic/item/4bed2e738bd4b31c1badd5a685d6277f9e2ff81e.jpg", + priority: Priority.HIGH + }, + { + loadSrc: "http://g.hiphotos.baidu.com/image/pic/item/0d338744ebf81a4c87a3add4d52a6059252da61e.jpg", + priority: Priority.HIGH + }, + { + loadSrc: "http://a.hiphotos.baidu.com/image/pic/item/f2deb48f8c5494ee5080c8142ff5e0fe99257e19.jpg", + priority: Priority.HIGH + }, + { + loadSrc: "http://f.hiphotos.baidu.com/image/pic/item/4034970a304e251f503521f5a586c9177e3e53f9.jpg", + priority: Priority.HIGH + }, + { + loadSrc: "https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658", + priority: Priority.MEDIUM + }, + { + loadSrc: "http://h.hiphotos.baidu.com/image/pic/item/7c1ed21b0ef41bd5f2c2a9e953da81cb39db3d1d.jpg", + priority: Priority.MEDIUM + }, + { + loadSrc: "http://a.hiphotos.baidu.com/image/pic/item/8d5494eef01f3a292d2472199d25bc315d607c7c.jpg", + priority: Priority.MEDIUM + }, + { + loadSrc: "https://img-blog.csdn.net/20140514114029140", + priority: Priority.MEDIUM + }, + { + loadSrc: "http://g.hiphotos.baidu.com/image/pic/item/6d81800a19d8bc3e770bd00d868ba61ea9d345f2.jpg", + priority: Priority.MEDIUM + }, + { + loadSrc: "http://b.hiphotos.baidu.com/image/pic/item/e824b899a9014c08878b2c4c0e7b02087af4f4a3.jpg", + priority: Priority.LOW + }, + { + loadSrc: "http://b.hiphotos.baidu.com/image/pic/item/359b033b5bb5c9ea5c0e3c23d139b6003bf3b374.jpg", + priority: Priority.LOW + }, + { + loadSrc: "http://h.hiphotos.baidu.com/image/pic/item/902397dda144ad340668b847d4a20cf430ad851e.jpg", + priority: Priority.LOW + }, + { + loadSrc: "http://d.hiphotos.baidu.com/image/pic/item/b58f8c5494eef01f119945cbe2fe9925bc317d2a.jpg", + priority: Priority.LOW + }, + { + loadSrc:"http://img2.xkhouse.com/bbs/hfhouse/data/attachment/forum/corebbs/2009-11/2009113011534566298.jpg", + priority: Priority.LOW + } +]; @Entry @Component struct TestPriorityComponent { - private data: string[] = [ - "http://e.hiphotos.baidu.com/image/pic/item/a1ec08fa513d2697e542494057fbb2fb4316d81e.jpg", - "http://c.hiphotos.baidu.com/image/pic/item/30adcbef76094b36de8a2fe5a1cc7cd98d109d99.jpg", - "http://h.hiphotos.baidu.com/image/pic/item/7c1ed21b0ef41bd5f2c2a9e953da81cb39db3d1d.jpg", - "http://g.hiphotos.baidu.com/image/pic/item/55e736d12f2eb938d5277fd5d0628535e5dd6f4a.jpg", - "http://e.hiphotos.baidu.com/image/pic/item/4e4a20a4462309f7e41f5cfe760e0cf3d6cad6ee.jpg", - "http://b.hiphotos.baidu.com/image/pic/item/9d82d158ccbf6c81b94575cfb93eb13533fa40a2.jpg", - "http://e.hiphotos.baidu.com/image/pic/item/4bed2e738bd4b31c1badd5a685d6277f9e2ff81e.jpg", - "http://g.hiphotos.baidu.com/image/pic/item/0d338744ebf81a4c87a3add4d52a6059252da61e.jpg", - "http://a.hiphotos.baidu.com/image/pic/item/f2deb48f8c5494ee5080c8142ff5e0fe99257e19.jpg", - "http://f.hiphotos.baidu.com/image/pic/item/4034970a304e251f503521f5a586c9177e3e53f9.jpg", - "http://b.hiphotos.baidu.com/image/pic/item/279759ee3d6d55fbb3586c0168224f4a20a4dd7e.jpg", - "http://img2.xkhouse.com/bbs/hfhouse/data/attachment/forum/corebbs/2009-11/2009113011534566298.jpg", - "http://a.hiphotos.baidu.com/image/pic/item/e824b899a9014c087eb617650e7b02087af4f464.jpg", - "http://c.hiphotos.baidu.com/image/pic/item/9c16fdfaaf51f3de1e296fa390eef01f3b29795a.jpg", - "http://d.hiphotos.baidu.com/image/pic/item/b58f8c5494eef01f119945cbe2fe9925bc317d2a.jpg", - "http://h.hiphotos.baidu.com/image/pic/item/902397dda144ad340668b847d4a20cf430ad851e.jpg", - "http://b.hiphotos.baidu.com/image/pic/item/359b033b5bb5c9ea5c0e3c23d139b6003bf3b374.jpg", - "http://a.hiphotos.baidu.com/image/pic/item/8d5494eef01f3a292d2472199d25bc315d607c7c.jpg", - "http://b.hiphotos.baidu.com/image/pic/item/e824b899a9014c08878b2c4c0e7b02087af4f4a3.jpg", - "http://g.hiphotos.baidu.com/image/pic/item/6d81800a19d8bc3e770bd00d868ba61ea9d345f2.jpg", - "https://hbimg.huabanimg.com/cc6af25f8d782d3cf3122bef4e61571378271145735e9-vEVggB", - 'https://img-blog.csdnimg.cn/20191215043500229.png', - 'https://img-blog.csdn.net/20140514114029140', - 'https://hbimg.huabanimg.com/95a6d37a39aa0b70d48fa18dc7df8309e2e0e8e85571e-x4hhks_fw658/format/webp' - ] + @State data: ImageKnifeOption[] = [ + { + loadSrc: "https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658", + priority: Priority.HIGH + }, + { + loadSrc: "http://c.hiphotos.baidu.com/image/pic/item/30adcbef76094b36de8a2fe5a1cc7cd98d109d99.jpg", + priority: Priority.HIGH + }, + { + loadSrc: "http://h.hiphotos.baidu.com/image/pic/item/7c1ed21b0ef41bd5f2c2a9e953da81cb39db3d1d.jpg", + priority: Priority.HIGH + }, + { + loadSrc: "http://g.hiphotos.baidu.com/image/pic/item/55e736d12f2eb938d5277fd5d0628535e5dd6f4a.jpg", + priority: Priority.HIGH + }, + { + loadSrc: "http://e.hiphotos.baidu.com/image/pic/item/4e4a20a4462309f7e41f5cfe760e0cf3d6cad6ee.jpg", + priority: Priority.HIGH + }, + { + loadSrc: "http://b.hiphotos.baidu.com/image/pic/item/279759ee3d6d55fbb3586c0168224f4a20a4dd7e.jpg", + priority: Priority.MEDIUM + }, + { + loadSrc: "http://img2.xkhouse.com/bbs/hfhouse/data/attachment/forum/corebbs/2009-11/2009113011534566298.jpg", + priority: Priority.MEDIUM + }, + { + loadSrc: "http://a.hiphotos.baidu.com/image/pic/item/e824b899a9014c087eb617650e7b02087af4f464.jpg", + priority: Priority.MEDIUM + }, + { + loadSrc: "http://c.hiphotos.baidu.com/image/pic/item/9c16fdfaaf51f3de1e296fa390eef01f3b29795a.jpg", + priority: Priority.MEDIUM + }, + { + loadSrc: "http://d.hiphotos.baidu.com/image/pic/item/b58f8c5494eef01f119945cbe2fe9925bc317d2a.jpg", + priority: Priority.MEDIUM + }, + { + loadSrc: "https://hbimg.huabanimg.com/cc6af25f8d782d3cf3122bef4e61571378271145735e9-vEVggB", + priority: Priority.LOW + }, + { + loadSrc: "https://img-blog.csdnimg.cn/20191215043500229.png", + priority: Priority.LOW + }, + { + loadSrc: "https://img-blog.csdn.net/20140514114029140", + priority: Priority.LOW + }, + { + loadSrc: "https://hbimg.huabanimg.com/95a6d37a39aa0b70d48fa18dc7df8309e2e0e8e85571e-x4hhks_fw658/format/webp", + priority: Priority.LOW + }, + { + loadSrc: "http://g.hiphotos.baidu.com/image/pic/item/6d81800a19d8bc3e770bd00d868ba61ea9d345f2.jpg", + priority: Priority.LOW + } + ]; + @State maxRequests: number | undefined = ImageKnifeGlobal?.getInstance()?.getImageKnife()?.maxRequests; - build(){ - Column(){ - Grid(){ - ForEach(this.data,(item:string,index:number)=>{ - GridItem(){ - if(index % 2 == 0){ - ImageKnifeComponent({ - imageKnifeOption:{ - loadSrc:item, - placeholderSrc:$r('app.media.icon_loading'), - strategy:new NONE(), - isCacheable:false, - priority:Priority.LOW - } - }) - } else if (index % 3 == 0) { - ImageKnifeComponent({ - imageKnifeOption:{ - loadSrc:item, - placeholderSrc:$r('app.media.icon_loading'), - strategy:new NONE(), - isCacheable:false, - priority:Priority.MEDIUM - } - }) - } else { - ImageKnifeComponent({ - imageKnifeOption:{ - loadSrc:item, - placeholderSrc:$r('app.media.icon_loading'), - strategy:new NONE(), - isCacheable:false, - priority:Priority.HIGH - } - }) + build() { + Scroll() { + Column() { + Text('最大并发数: ' + this.maxRequests).width('50%').height(30) + Button('修改最大并发数为1').width('50%').height(30) + .onClick(() => { + if (ImageKnifeGlobal?.getInstance()?.getImageKnife()) { + ImageKnifeGlobal?.getInstance()?.getImageKnife()?.setMaxRequests(1); + this.maxRequests = ImageKnifeGlobal?.getInstance()?.getImageKnife()?.maxRequests; + this.data = [...dataBak, ...dataBak, ...dataBak]; } - } - }) + }) + Grid() { + ForEach(this.data, (item: ImageKnifeOption, index: number) => { + GridItem() { + Flex() { + Text(item?.priority?.toString() || '') + ImageKnifeComponent({ + imageKnifeOption: { + loadSrc: item.loadSrc, + placeholderSrc: $r('app.media.icon_loading'), + strategy: new NONE(), + isCacheable: false, + priority: item.priority + } + }) + }.height(60) + } + }) + } + .width("100%") + .height("100%") + .columnsTemplate("1fr 1fr 1fr 1fr 1fr") + .columnsGap(10) + .rowsGap(10) + .backgroundColor(0xFAEEE0) + .maxCount(10) } - .width("100%") - .height("100%") - .columnsTemplate("1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr") - .rowsTemplate("1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr") - .columnsGap(10) - .backgroundColor(0xFAEEE0) - }.width("100%").height("100%").backgroundColor(Color.Pink) + .backgroundColor(Color.Pink) + .margin({ top: 5 }) + } } } \ No newline at end of file diff --git a/entry/src/ohosTest/ets/test/DefaultJobQueueTest.test.ets b/entry/src/ohosTest/ets/test/DefaultJobQueueTest.test.ets new file mode 100644 index 0000000..e1c1401 --- /dev/null +++ b/entry/src/ohosTest/ets/test/DefaultJobQueueTest.test.ets @@ -0,0 +1,64 @@ +/* + * 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 { describe, it, expect } from '@ohos/hypium'; +import { ImageKnife, ImageKnifeGlobal, RequestOption } from '@ohos/imageknife/Index'; +import { DefaultJobQueue } from '@ohos/imageknife/src/main/ets/components/imageknife/utils/DefaultJobQueue'; +import { IJobQueue } from '@ohos/imageknife/src/main/ets/components/imageknife/utils/IJobQueue'; +import taskpool from '@ohos.taskpool'; +import common from '@ohos.app.ability.common'; + +export default function DefaultJobQueueTest() { + + describe('DefaultJobQueueTest', () => { + it('testJob', 0, () => { + let job: IJobQueue = new DefaultJobQueue(); + job.add(makeRequest("medium1", getContext() as common.UIAbilityContext)); + job.add(makeRequest("medium2", getContext() as common.UIAbilityContext, taskpool.Priority.MEDIUM)); + job.add(makeRequest("medium3", getContext() as common.UIAbilityContext)); + job.add(makeRequest("low1", getContext() as common.UIAbilityContext, taskpool.Priority.LOW)); + job.add(makeRequest("high1", getContext() as common.UIAbilityContext, taskpool.Priority.HIGH)); + job.add(makeRequest("low2", getContext() as common.UIAbilityContext, taskpool.Priority.LOW)); + job.add(makeRequest("medium4", getContext() as common.UIAbilityContext)); + + expect(job.getQueueLength()).assertEqual(7); + expect(job.pop()!.loadSrc).assertEqual("high1"); + expect(job.pop()!.loadSrc).assertEqual("medium1"); + expect(job.pop()!.loadSrc).assertEqual("medium2"); + expect(job.pop()!.loadSrc).assertEqual("medium3"); + expect(job.pop()!.loadSrc).assertEqual("medium4"); + expect(job.pop()!.loadSrc).assertEqual("low1"); + expect(job.pop()!.loadSrc).assertEqual("low2"); + expect(job.pop()).assertEqual(undefined); + expect(job.getQueueLength()).assertEqual(0); + }); + + it("testMaxRequests", 1, () => { + let imageKnife: ImageKnife | undefined = ImageKnifeGlobal.getInstance().getImageKnife(); + if (imageKnife) { + expect(imageKnife.maxRequests).assertEqual(64); + imageKnife.setMaxRequests(10); + expect(imageKnife.maxRequests).assertEqual(10); + }; + }) + }); +} + +function makeRequest(src: string, context: common.UIAbilityContext, priority: taskpool.Priority = taskpool.Priority.MEDIUM): RequestOption { + let request: RequestOption = new RequestOption() + request.loadSrc = src; + request.priority = priority; + request.moduleContext = context; + return request; +} diff --git a/entry/src/ohosTest/ets/test/List.test.ets b/entry/src/ohosTest/ets/test/List.test.ets index 489b341..29810cc 100644 --- a/entry/src/ohosTest/ets/test/List.test.ets +++ b/entry/src/ohosTest/ets/test/List.test.ets @@ -19,6 +19,7 @@ import Transfrom from './transfrom.test' import RequestOptionTest from './requestoption.test' import ImageKnifeTest from './imageknife.test' import DiskLruCacheTest from './diskLruCache.test' +import DefaultJobQueueTest from './DefaultJobQueueTest.test'; export default function testsuite() { abilityTest() @@ -28,4 +29,5 @@ export default function testsuite() { Transfrom() RequestOptionTest() ImageKnifeTest(); + DefaultJobQueueTest(); } \ 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 35fa987..3e784a1 100644 --- a/library/src/main/ets/components/imageknife/ImageKnife.ets +++ b/library/src/main/ets/components/imageknife/ImageKnife.ets @@ -35,13 +35,16 @@ import http from '@ohos.net.http' import { CompressBuilder } from "../imageknife/compress/CompressBuilder" import { IDrawLifeCycle } from '../imageknife/interface/IDrawLifeCycle' 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 { MemoryLruCache } from '../cache/MemoryLruCache' import { BusinessError } from '@ohos.base' import taskpool from '@ohos.taskpool' +import { DefaultJobQueue } from './utils/DefaultJobQueue'; +import { IJobQueue } from './utils/IJobQueue'; +import LightWeightMap from '@ohos.util.LightWeightMap'; +import List from '@ohos.util.List'; import { GIFFrame } from './utils/gif/GIFFrame' import emitter from '@ohos.events.emitter'; @@ -68,9 +71,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; // 通用文件格式辨别 @@ -87,14 +87,16 @@ export class ImageKnife { // 开发者可配置全局缓存 engineKeyImpl: EngineKeyInterface; private mParseImageUtil: IParseImage; + // 最大并发 + maxRequests: number = 64; + // 排队队列 + private jobQueue: IJobQueue = new DefaultJobQueue(); + // 执行中的请求 + private executingJobMap: LightWeightMap> = new LightWeightMap(); 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); @@ -204,7 +206,6 @@ export class ImageKnife { return new CompressBuilder(); } - // 设置缓存张数,缓存大小,单位字节 public setLruCacheSize(size: number, memory: number) { if (this.memoryCache.map.size() <= 0) { @@ -248,28 +249,6 @@ 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()) }) } @@ -277,28 +256,19 @@ 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 + if (this.executingJobMap.length > 0) { + this.executingJobMap.forEach((list: List) => { + this.taskpoolLoadResource(list.get(0), Constants.MAIN_HOLDER); + }) + } else { + this.dispatchNextJob(); } - this.pausedMaps.clear() - LogUtil.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) - } + this.executingJobMap.remove(uuid); } // 正常加载 @@ -525,111 +495,38 @@ 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); - } - } - - // 执行相同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(); + if (!this.isPaused) { + //不暂停则继续加载 + this.executingJobMap.remove(request.uuid); + this.dispatchNextJob(); } } // 启动新线程 去磁盘取 去网络取 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 (hasRunningRequest) { - this.pendingMaps.put(request.uuid, request); - - - } else { - this.runningMaps.put(request.uuid, request) - + if (this.keyNotEmpty(request)) { + if (this.executingJobMap.length > this.maxRequests) { + this.jobQueue.add(request); + return + } + 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) { + LogUtil.log("loadCacheManager start uuid : " + request.uuid + " url : " + request.loadSrc); + //暂停则不开启加载 this.taskpoolLoadResource(request, Constants.MAIN_HOLDER); } + } else { + requestList.add(request); + this.executingJobMap.set(request.uuid, requestList); + LogUtil.log("loadCacheManager reuse uuid : " + request.uuid + " url : " + request.loadSrc); + return; } - else { - LogUtil.log("key没有生成无法进入存取!") - } + } else { + LogUtil.log("key没有生成无法进入存取!"); } } @@ -726,7 +623,12 @@ export class ImageKnife { } else if (usageType == Constants.MAIN_HOLDER && mainCache) { LogUtil.info("imageknife load mainsource from MemoryCache") mainCache.waitSaveDisk = false; - request.loadComplete(mainCache); + let requestList: List | undefined = this.executingJobMap.get(request.uuid); + if(requestList != undefined) { + requestList.forEach((requestOption: RequestOption)=>{ + requestOption.loadComplete(mainCache as ImageKnifeData); + }) + } return; } //组装可以通过sendable传递的数据 @@ -772,12 +674,22 @@ export class ImageKnife { } 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) + if(requestList != undefined) { + requestList.forEach((requestOption: RequestOption)=>{ + requestOption.loadComplete(imageKnifeData); + }) + } + this.memoryCacheProxy.putValue(request.generateCacheKey, imageKnifeData); } 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); + if(requestList != undefined) { + requestList.forEach((requestOption: RequestOption)=>{ + requestOption.loadComplete(imageKnifeData); + }) + } + this.memoryCacheProxy.putValue(request.generateCacheKey, imageKnifeData); } else { request.loadError("request resources error") } @@ -785,7 +697,6 @@ export class ImageKnife { }).catch((err: BusinessError | string) => { request.loadError(err) }) - } private keyNotEmpty(request: RequestOption): boolean { @@ -799,40 +710,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) @@ -877,6 +754,21 @@ export class ImageKnife { }) } + + // 分发下一个任务 + dispatchNextJob() { + let request: RequestOption | undefined = this.jobQueue.pop(); + if (request != undefined) { + this.loadCacheManager(request); + } + } + + // 设置请求的最大并发数量 + setMaxRequests(count: number) { + if (count > 0) { + this.maxRequests = count; + } + } } diff --git a/library/src/main/ets/components/imageknife/RequestOption.ets b/library/src/main/ets/components/imageknife/RequestOption.ets index a01a6ad..0d2b0f9 100644 --- a/library/src/main/ets/components/imageknife/RequestOption.ets +++ b/library/src/main/ets/components/imageknife/RequestOption.ets @@ -183,9 +183,6 @@ export class RequestOption { constructor() { // 初始化全局监听 this.requestListeners = new Array(); - // 初始化唯一标识,可以用这个标识找到ImageKnife中的对象 - this.uuid = this.generateUUID(); - let ctx = ImageKnifeGlobal.getInstance().getHapContext() as common.UIAbilityContext if (ctx != undefined) { @@ -199,14 +196,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) { @@ -244,6 +234,8 @@ export class RequestOption { load(src: string | PixelMap | Resource) { this.loadSrc = src; + // 初始化唯一标识,可以用这个标识找到ImageKnife中的对象 + this.uuid = this.generateUUID(); return this; } 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..0009872 --- /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 '@ohos.taskpool'; +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..e847744 --- /dev/null +++ b/library/src/main/ets/components/imageknife/utils/IJobQueue.ets @@ -0,0 +1,36 @@ +/* + * 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 '../../imageknife/RequestOption'; + +export interface IJobQueue { + + /** + * 获取队列长度 + * @returns + */ + getQueueLength(): number; + + /** + * 往队列中添加元素 + * @param RequestObject + */ + add(request: RequestOption): void; + + /** + * 移除并返回队列头部的元素 + * @returns + */ + pop(): RequestOption | undefined; +}