更新说明:

1、优化请求队列结构,提升加载性能
2、移除不必要的注释代码

Signed-off-by: 明月清风 <qiufeihu1@h-partners.com>
This commit is contained in:
明月清风 2024-04-19 10:55:11 +08:00
parent 95a2526408
commit 2f9b3242f9
4 changed files with 151 additions and 194 deletions

View File

@ -53,8 +53,13 @@ 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';
export class ImageKnife {
private TAG:string = "ImageKnife";
static readonly SEPARATOR: string = '/'
memoryCache: MemoryLruCache;
dataFetch: IDataFetch;
@ -64,9 +69,6 @@ export class ImageKnife {
memoryCacheProxy: MemoryCacheProxy<string, ImageKnifeData> = new MemoryCacheProxy(new MemoryLruCache(100, 100 * 1024 * 1024));
headerMap: Map<string, Object> = new Map<string, Object>(); //定义全局map
placeholderCache: string = "placeholderCache"
runningMaps: EasyLinkedHashMap<string, RequestOption>;
pendingMaps: EasyLinkedHashMap<string, RequestOption>;
pausedMaps: EasyLinkedHashMap<string, RequestOption>;
isPaused: boolean = false;
mutex: MethodMutex = new MethodMutex();
fileTypeUtil: FileTypeUtil; // 通用文件格式辨别
@ -77,6 +79,14 @@ export class ImageKnife {
}
}; // 全局监听器
// 排队队列
private jobQueue: IJobQueue = new DefaultJobQueue()
// 执行中的请求
private executingJobMap: LightWeightMap<string, List<RequestOption>> = new LightWeightMap();
// 最大并发
private maxRequests: number = 64
//相同任务等待队列
pendingJobMap: LightWeightMap<string, List<RequestOption>> = new LightWeightMap();
// gifWorker
gifWorker: worker.ThreadWorker | undefined = undefined;
defaultLifeCycle: IDrawLifeCycle | undefined = undefined;
@ -87,10 +97,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);
@ -222,15 +228,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资源实现二级缓存
@ -244,28 +241,7 @@ export class ImageKnife {
async pauseRequests(): Promise<void> {
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')
})
}
@ -273,28 +249,15 @@ export class ImageKnife {
async resumeRequests(): Promise<void> {
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)
}
// 正常加载
@ -517,112 +480,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<RequestOption> | 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没有生成无法进入存取")
}
}
}
//组装任务参数
@ -703,12 +603,12 @@ export class ImageKnife {
request.progressFunc.asyncSuccess(percent);
}
});
taskpool.execute(task,request.priority).then((data: ESObject) => {
taskpool.execute(task, request.priority).then((data: ESObject) => {
if (usageType == Constants.PLACE_HOLDER) {
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")
}
@ -716,7 +616,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")
}
@ -724,20 +624,26 @@ 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<RequestOption> | 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<RequestOption> | 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")
@ -749,7 +655,7 @@ export class ImageKnife {
}
private setDiskCache(request: RequestOption):void{
private setDiskCache(request: RequestOption): void {
try {
// let diskMemoryCache = ImageKnifeGlobal.getInstance().getImageKnife()?.getDiskMemoryCache();
let dataArraybuffer: ArrayBuffer = DiskLruCache.getFileCacheByFile(this.diskMemoryCache.getPath() as string, request.generateDataKey) as ArrayBuffer;
@ -779,40 +685,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)
@ -857,6 +729,13 @@ export class ImageKnife {
})
}
setMaxRequests(concurrency: number): void {
if (concurrency > 0) {
this.maxRequests = concurrency
}
}
}

View File

@ -182,14 +182,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) {

View File

@ -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<RequestOption> = new Queue();
normalQueue: Queue<RequestOption> = new Queue();
lowQueue: Queue<RequestOption> = 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()
}
}
}

View File

@ -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
}