更新说明:
1、优化请求队列结构,提升加载性能 2、移除不必要的注释代码 Signed-off-by: 明月清风 <qiufeihu1@h-partners.com>
This commit is contained in:
parent
95a2526408
commit
2f9b3242f9
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue