From b57194cf4cebb1af85475be371b1f30a752ba389 Mon Sep 17 00:00:00 2001 From: tyBrave Date: Fri, 25 Oct 2024 22:48:53 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=8E=A5=E5=8F=A3=E5=9B=9E?= =?UTF-8?q?=E8=B0=83=E4=BF=A1=E6=81=AF=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: tyBrave --- library/src/main/ets/ImageKnife.ets | 4 +- library/src/main/ets/ImageKnifeDispatcher.ets | 148 +++++++++++-- library/src/main/ets/ImageKnifeLoader.ets | 204 ++++++++++++++---- library/src/main/ets/model/ImageKnifeData.ets | 48 ++++- .../src/main/ets/model/ImageKnifeOption.ets | 8 +- .../src/main/ets/model/ImageKnifeRequest.ets | 11 +- library/src/main/ets/utils/Constants.ets | 56 +++++ 7 files changed, 415 insertions(+), 64 deletions(-) diff --git a/library/src/main/ets/ImageKnife.ets b/library/src/main/ets/ImageKnife.ets index 41038ee..10d9faa 100644 --- a/library/src/main/ets/ImageKnife.ets +++ b/library/src/main/ets/ImageKnife.ets @@ -346,7 +346,7 @@ export class ImageKnife { * @param cacheType * @returns */ - getCacheUpperLimit(cacheType?: CacheStrategy): number | undefined { + getCacheLimitSize(cacheType?: CacheStrategy): number | undefined { if (cacheType == undefined || cacheType == CacheStrategy.Default) { cacheType = CacheStrategy.Memory; } @@ -366,7 +366,7 @@ export class ImageKnife { * @param cacheType * @returns */ - getCurrentPicturesNum(cacheType: CacheStrategy): number | undefined { + getCurrentCacheNum(cacheType: CacheStrategy): number | undefined { if (cacheType == undefined || cacheType == CacheStrategy.Default) { cacheType = CacheStrategy.Memory; } diff --git a/library/src/main/ets/ImageKnifeDispatcher.ets b/library/src/main/ets/ImageKnifeDispatcher.ets index 0272046..8de5152 100644 --- a/library/src/main/ets/ImageKnifeDispatcher.ets +++ b/library/src/main/ets/ImageKnifeDispatcher.ets @@ -19,10 +19,10 @@ import List from '@ohos.util.List'; import LightWeightMap from '@ohos.util.LightWeightMap'; import { LogUtil } from './utils/LogUtil'; import { ImageKnife } from './ImageKnife'; -import { ImageKnifeData, CacheStrategy } from './model/ImageKnifeData'; +import { ImageKnifeData, CacheStrategy, TimeInfo, ErrorInfo } from './model/ImageKnifeData'; import image from '@ohos.multimedia.image'; import emitter from '@ohos.events.emitter'; -import { Constants } from './utils/Constants'; +import { Constants, LoadPhase, LoadPixelMapCode } from './utils/Constants'; import taskpool from '@ohos.taskpool'; import { FileTypeUtil } from './utils/FileTypeUtil'; import { IEngineKey } from './key/IEngineKey'; @@ -50,6 +50,7 @@ export class ImageKnifeDispatcher { showFromMemomry(request: ImageKnifeRequest, imageSrc: string | PixelMap | Resource, requestSource: ImageKnifeRequestSource,isAnimator?: boolean): boolean { LogUtil.log("ImageKnife_DataTime_showFromMemomry.start:" + request.imageKnifeOption.loadSrc + "requestSource=" + requestSource + " isAnimator=" + isAnimator) let memoryCache: ImageKnifeData | undefined; + let memoryCheckStartTime = Date.now(); if ((typeof (request.imageKnifeOption.loadSrc as image.PixelMap).isEditable) == 'boolean') { memoryCache = { source: request.imageKnifeOption.loadSrc as image.PixelMap, @@ -61,12 +62,25 @@ export class ImageKnifeDispatcher { .loadFromMemoryCache(this.engineKey.generateMemoryKey(imageSrc, requestSource, request.imageKnifeOption,isAnimator)); } + //记录ImageKnifeRequestSource.SRC 开始内存检查的时间点 + if (requestSource == ImageKnifeRequestSource.SRC && request.getImageKnifeData()) { + let timeInfo = request.getImageKnifeData()?.timeInfo + if (timeInfo) { + timeInfo.memoryCheckStartTime = memoryCheckStartTime; + timeInfo.memoryCheckEndTime = Date.now(); + //设置请求结束的时间点 + if (memoryCache !== undefined) { + timeInfo.requestEndTime = Date.now(); + } + } + } + if (memoryCache !== undefined) { // 画主图 if (request.requestState === ImageKnifeRequestState.PROGRESS) { // 回调请求开始 if (requestSource === ImageKnifeRequestSource.SRC && request.imageKnifeOption.onLoadListener?.onLoadStart !== undefined) { - request.imageKnifeOption.onLoadListener.onLoadStart() + request.imageKnifeOption.onLoadListener.onLoadStart(request) LogUtil.log("ImageKnife_DataTime_MemoryCache_onLoadStart:" + request.imageKnifeOption.loadSrc) } LogUtil.log("ImageKnife_DataTime_MemoryCache_showPixelMap.start:" + request.imageKnifeOption.loadSrc) @@ -77,7 +91,8 @@ export class ImageKnifeDispatcher { request.requestState = ImageKnifeRequestState.COMPLETE // 回调请求开结束 if (request.imageKnifeOption.onLoadListener?.onLoadSuccess !== undefined) { - request.imageKnifeOption.onLoadListener.onLoadSuccess(memoryCache.source,memoryCache) + this.copyMemoryCacheInfo(memoryCache, request.getImageKnifeData()); + request.imageKnifeOption.onLoadListener.onLoadSuccess(memoryCache.source, memoryCache, request) LogUtil.log("ImageKnife_DataTime_MemoryCache_onLoadSuccess:" + request.imageKnifeOption.loadSrc) } } else if (requestSource == ImageKnifeRequestSource.ERROR_HOLDER) { @@ -91,8 +106,55 @@ export class ImageKnifeDispatcher { return false } + private copyMemoryCacheInfo(memoryCache: ImageKnifeData | undefined, target: ImageKnifeData | undefined) { + if (!memoryCache || !target) { + return; + } + target.source = memoryCache.source; + target.imageWidth = memoryCache.imageWidth; + target.imageHeight = memoryCache.imageHeight; + target.type = memoryCache.type; + target.imageAnimator = memoryCache.imageAnimator; + } + + private assembleImageKnifeData(beforeCallData: ImageKnifeData | undefined, afterCallData: ImageKnifeData | undefined, req: ImageKnifeRequest) { + if (!beforeCallData || !afterCallData || !req) { + return; + } + //设置图片开始加载时间及其缓存检查时间点 + if (beforeCallData.timeInfo) { + if (afterCallData.timeInfo) { + afterCallData.timeInfo.requestStartTime = beforeCallData.timeInfo.requestStartTime; + afterCallData.timeInfo.memoryCheckStartTime = beforeCallData.timeInfo.memoryCheckStartTime; + afterCallData.timeInfo.memoryCheckEndTime = beforeCallData.timeInfo.memoryCheckEndTime; + } + } + req.setImageKnifeData(afterCallData); + } + + private initCallData(request: ImageKnifeRequest) { + if (!request) { + return + } + //图片加载信息回调数据 + let callBackData: ImageKnifeData = { + source: "", + imageWidth: 0, + imageHeight: 0, + }; + + //图片加载信息回调数据时间点 + let callBackTimeInfo: TimeInfo = {}; + callBackTimeInfo.requestStartTime = Date.now(); + callBackData.timeInfo = callBackTimeInfo; + + //跟隨請求保存回調信息點 + request.setImageKnifeData(callBackData); + } enqueue(request: ImageKnifeRequest,isAnimator?: boolean): void { + //初始化加载回调信息 + this.initCallData(request); //1.内存有的话直接渲染 if (this.showFromMemomry(request, request.imageKnifeOption.loadSrc, ImageKnifeRequestSource.SRC,isAnimator)) { return @@ -129,7 +191,7 @@ export class ImageKnifeDispatcher { getAndShowImage(currentRequest: ImageKnifeRequest, imageSrc: string | PixelMap | Resource, requestSource: ImageKnifeRequestSource,isAnimator?: boolean): void { LogUtil.log("ImageKnife_DataTime_getAndShowImage.start:" + currentRequest.imageKnifeOption.loadSrc) if (requestSource === ImageKnifeRequestSource.SRC && currentRequest.imageKnifeOption.onLoadListener?.onLoadStart !== undefined) { - currentRequest.imageKnifeOption.onLoadListener?.onLoadStart() + currentRequest.imageKnifeOption.onLoadListener?.onLoadStart(currentRequest) LogUtil.log("ImageKnife_DataTime_getAndShowImage_onLoadStart:" + currentRequest.imageKnifeOption.loadSrc) } @@ -152,7 +214,7 @@ export class ImageKnifeDispatcher { // 回调请求开始 requestList.forEach((requestWithSource: ImageKnifeRequestWithSource) => { if (requestWithSource.source === ImageKnifeRequestSource.SRC && requestWithSource.request.imageKnifeOption.onLoadListener?.onLoadStart !== undefined) { - requestWithSource.request.imageKnifeOption.onLoadListener?.onLoadStart() + requestWithSource.request.imageKnifeOption.onLoadListener?.onLoadStart(requestWithSource.request) LogUtil.log("ImageKnife_DataTime_getAndShowImage_onLoadStart:" + currentRequest.imageKnifeOption.loadSrc) } if (requestWithSource.request.imageKnifeOption.progressListener !== undefined && requestWithSource.source === ImageKnifeRequestSource.SRC) { @@ -272,6 +334,12 @@ export class ImageKnifeDispatcher { if (requestJobResult === undefined){ return } + + //设置请求结束的时间 + if (requestJobResult.imageKnifeData && requestJobResult.imageKnifeData.timeInfo) { + requestJobResult.imageKnifeData.timeInfo.requestEndTime = Date.now(); + } + let pixelmap = requestJobResult.pixelMap; if (pixelmap === undefined) { LogUtil.log("ImageKnife_DataTime_getAndShowImage_CallBack.pixelmap undefined:"+currentRequest.imageKnifeOption.loadSrc) @@ -280,6 +348,7 @@ export class ImageKnifeDispatcher { if (requestWithSource.source === ImageKnifeRequestSource.SRC && requestWithSource.request.imageKnifeOption.onLoadListener?.onLoadFailed !== undefined && requestJobResult.loadFail) { + this.assembleImageKnifeData(requestWithSource.request.getImageKnifeData(), requestJobResult.imageKnifeData, requestWithSource.request) requestWithSource.request.imageKnifeOption.onLoadListener.onLoadFailed(requestJobResult.loadFail,requestWithSource.request); LogUtil.log("ImageKnife_DataTime_getAndShowImage_onLoadFailed:"+currentRequest.imageKnifeOption.loadSrc) } @@ -304,12 +373,19 @@ export class ImageKnifeDispatcher { LogUtil.log("ImageKnife_DataTime_getAndShowImage_saveWithoutWriteFile.end:"+currentRequest.imageKnifeOption.loadSrc) } - let ImageKnifeData: ImageKnifeData = { - source: pixelmap!, - imageWidth: requestJobResult.size == undefined ? 0 : requestJobResult.size.width, - imageHeight: requestJobResult.size == undefined ? 0 : requestJobResult.size.height, - type:requestJobResult.type - }; + let imageKnifeData: ImageKnifeData; + if (!requestJobResult.imageKnifeData) { + imageKnifeData = { + source: pixelmap!, + imageWidth: requestJobResult.size == undefined ? 0 : requestJobResult.size.width, + imageHeight: requestJobResult.size == undefined ? 0 : requestJobResult.size.height, + type: requestJobResult.type, + }; + } else { + imageKnifeData = requestJobResult.imageKnifeData; + imageKnifeData.source = pixelmap!; + } + if(requestJobResult.pixelMapList != undefined) { let imageAnimator: Array = [] requestJobResult.pixelMapList.forEach((item,index)=>{ @@ -318,14 +394,24 @@ export class ImageKnifeDispatcher { duration:requestJobResult.delayList![index] }) }) - ImageKnifeData.imageAnimator = imageAnimator + imageKnifeData.imageAnimator = imageAnimator } + + //构建缓存保存的ImageKnifeData + let saveCacheImageData: ImageKnifeData = { + source: pixelmap!, + imageWidth: requestJobResult.size == undefined ? 0 : requestJobResult.size.width, + imageHeight: requestJobResult.size == undefined ? 0 : requestJobResult.size.height, + type: requestJobResult.type, + imageAnimator: imageKnifeData.imageAnimator + } + // 保存内存缓存 if (currentRequest.imageKnifeOption.writeCacheStrategy !== CacheStrategy.File) { LogUtil.log("ImageKnife_DataTime_getAndShowImage_saveMemoryCache.start:"+currentRequest.imageKnifeOption.loadSrc) ImageKnife.getInstance() .saveMemoryCache(this.engineKey.generateMemoryKey(imageSrc, requestSource, currentRequest.imageKnifeOption,isAnimator), - ImageKnifeData); + saveCacheImageData); LogUtil.log("ImageKnife_DataTime_getAndShowImage_saveMemoryCache.end:"+currentRequest.imageKnifeOption.loadSrc) } if (requestList !== undefined) { @@ -339,7 +425,7 @@ export class ImageKnifeDispatcher { requestWithSource.request.requestState === ImageKnifeRequestState.PROGRESS)) { LogUtil.log("ImageKnife_DataTime_getAndShowImage_showPixelMap.start:"+currentRequest.imageKnifeOption.loadSrc) requestWithSource.request.ImageKnifeRequestCallback.showPixelMap(requestWithSource.request.componentVersion, - ImageKnifeData.source, requestWithSource.source,ImageKnifeData.imageAnimator); + imageKnifeData.source, requestWithSource.source,imageKnifeData.imageAnimator); LogUtil.log("ImageKnife_DataTime_getAndShowImage_showPixelMap.end:"+currentRequest.imageKnifeOption.loadSrc) } @@ -348,7 +434,9 @@ export class ImageKnifeDispatcher { if (requestWithSource.request.imageKnifeOption.onLoadListener && requestWithSource.request.imageKnifeOption.onLoadListener.onLoadSuccess) { // 回调请求成功 - requestWithSource.request.imageKnifeOption.onLoadListener.onLoadSuccess(ImageKnifeData.source,ImageKnifeData); + this.assembleImageKnifeData(requestWithSource.request.getImageKnifeData(), imageKnifeData,requestWithSource.request); + requestWithSource.request.imageKnifeOption.onLoadListener.onLoadSuccess(imageKnifeData.source, + imageKnifeData, requestWithSource.request); LogUtil.log("ImageKnife_DataTime_getAndShowImage_onLoadSuccess:"+currentRequest.imageKnifeOption.loadSrc) } } else if (requestWithSource.source == ImageKnifeRequestSource.ERROR_HOLDER) { @@ -357,7 +445,19 @@ export class ImageKnifeDispatcher { } else { if (requestWithSource.source == ImageKnifeRequestSource.SRC && requestWithSource.request.imageKnifeOption.onLoadListener?.onLoadCancel) { // 回调请求成功 - requestWithSource.request.imageKnifeOption.onLoadListener.onLoadCancel("component has destroyed") + // 回调请求成功 + //设置失败回调的时间点 + let callBackData = requestWithSource.request.getImageKnifeData(); + + if (requestJobResult.imageKnifeData && requestJobResult.imageKnifeData.timeInfo) { + requestJobResult.imageKnifeData.timeInfo.requestCancelTime = Date.now(); + if (requestJobResult.imageKnifeData.errorInfo) { + requestJobResult.imageKnifeData.errorInfo.phase = LoadPhase.PHASE_WILL_SHOW; + requestJobResult.imageKnifeData.errorInfo.code = LoadPixelMapCode.IMAGE_LOAD_CANCEL_FAILED_CODE; + } + } + this.assembleImageKnifeData(callBackData,requestJobResult.imageKnifeData,requestWithSource.request) + requestWithSource.request.imageKnifeOption.onLoadListener.onLoadCancel("component has destroyed", requestWithSource.request) } } }); @@ -385,7 +485,19 @@ export class ImageKnifeDispatcher { LogUtil.log("ImageKnife_DataTime_dispatchNextJob.end executeJob:" + request.imageKnifeOption.loadSrc) break }else if (request.requestState == ImageKnifeRequestState.DESTROY && request.imageKnifeOption.onLoadListener?.onLoadCancel) { - request.imageKnifeOption.onLoadListener.onLoadCancel("component has destroyed") + //构建回调错误信息 + let callBackData = request.getImageKnifeData(); + if (callBackData) { + let timeInfo: TimeInfo = ImageKnifeLoader.getTimeInfo(callBackData) + timeInfo.requestCancelTime = Date.now(); + timeInfo.requestEndTime = Date.now() + let errorInfo: ErrorInfo = { + phase: LoadPhase.PHASE_THREAD_QUEUE, + code: LoadPixelMapCode.IMAGE_LOAD_CANCEL_FAILED_CODE, + }; + callBackData.errorInfo = errorInfo; + } + request.imageKnifeOption.onLoadListener.onLoadCancel("component has destroyed", request) } } } diff --git a/library/src/main/ets/ImageKnifeLoader.ets b/library/src/main/ets/ImageKnifeLoader.ets index f8cd230..6c359a9 100644 --- a/library/src/main/ets/ImageKnifeLoader.ets +++ b/library/src/main/ets/ImageKnifeLoader.ets @@ -14,12 +14,16 @@ */ import { CacheStrategy, + DecodeImageInfo, + ErrorInfo, + ImageKnifeData, ImageKnifeRequestSource, - ImageKnifeRequestWithSource, RequestJobRequest } from './model/ImageKnifeData'; + ImageKnifeRequestWithSource, RequestJobRequest, + TimeInfo } from './model/ImageKnifeData'; import List from '@ohos.util.List' import { FileCache } from './cache/FileCache'; import { LogUtil } from './utils/LogUtil'; -import { Constants } from './utils/Constants'; +import { Constants, LoadPhase, LoadPixelMapCode } from './utils/Constants'; import http from '@ohos.net.http'; import { combineArrayBuffers } from './utils/ArrayBufferUtils'; import { BusinessError } from '@kit.BasicServicesKit'; @@ -43,58 +47,94 @@ export class ImageKnifeLoader { ImageKnifeLoader.getImageArrayBuffer(request,requestList,fileKey) } static async parseImage(resBuf: ArrayBuffer, fileKey: string, - request: RequestJobRequest) { + request: RequestJobRequest, callBackData: ImageKnifeData) { + callBackData.bufSize = resBuf.byteLength; let typeValue = new FileTypeUtil().getFileType(resBuf); if(typeValue == null) { LogUtil.log("ImageKnife_DataTime_requestJob.end: getFileType is null " + request.src) - ImageKnifeLoader.makeEmptyResult(request,"request is not a valid image source") + ImageKnifeLoader.makeEmptyResult(request,"request is not a valid image source", ImageKnifeLoader.assembleError(callBackData, LoadPhase.PHASE_GET_FORMAT, LoadPixelMapCode.IMAGE_PARSE_FORMAT_FAILED_CODE)) return } + callBackData.type = typeValue; if(request.isAnimator) { - ImageKnifeLoader.parseForAnimatorComponent(resBuf ,typeValue ,fileKey, request) + ImageKnifeLoader.parseForAnimatorComponent(resBuf ,typeValue ,fileKey, request, callBackData) return } if (typeValue === 'gif' || typeValue === 'webp') { - ImageKnifeLoader.parseAnimatorImage(resBuf ,typeValue ,fileKey , request) + ImageKnifeLoader.parseAnimatorImage(resBuf ,typeValue ,fileKey , request, callBackData) return } else if(typeValue == "svg") { - ImageKnifeLoader.parseSvgImage(resBuf ,typeValue ,fileKey , request) + ImageKnifeLoader.parseSvgImage(resBuf ,typeValue ,fileKey , request, callBackData) return } - ImageKnifeLoader.parseNormalImage(resBuf, typeValue, fileKey, request) + ImageKnifeLoader.parseNormalImage(resBuf, typeValue, fileKey, request, callBackData) } - static makeEmptyResult(request:RequestJobRequest,error: string){ + static makeEmptyResult(request:RequestJobRequest,error: string, data?: ImageKnifeData){ let res: RequestJobResult = { pixelMap: undefined, bufferSize: 0, fileKey: '', loadFail: error, + imageKnifeData: data } emitter.emit(Constants.CALLBACK_EMITTER + request.memoryKey, { data: { "value": res } }) } - static async parseNormalImage(resBuf: ArrayBuffer, typeValue: string, fileKey: string, request: RequestJobRequest) { + static assembleError(data: ImageKnifeData | undefined, phase: string, code?: number, + httpCode?: number): ImageKnifeData | undefined { + let errorCallBackData = data?.errorInfo; + if (!errorCallBackData) { + return data; + } + errorCallBackData.phase = phase; + errorCallBackData.code = code? code: 0; + if (httpCode && httpCode != 0) { + errorCallBackData.httpCode = httpCode; + } + return data + } + + static getTimeInfo(callBackData: ImageKnifeData): TimeInfo { + let timeInfo: TimeInfo; + if (callBackData.timeInfo) { + timeInfo = callBackData.timeInfo; + }else { + timeInfo = {}; + callBackData.timeInfo = timeInfo; + } + return timeInfo; + } + + static async parseNormalImage(resBuf: ArrayBuffer, typeValue: string, fileKey: string, request: RequestJobRequest, callBackData: ImageKnifeData) { let resPixelmap: PixelMap | undefined = undefined + let timeInfo: TimeInfo = ImageKnifeLoader.getTimeInfo(callBackData); + let decodingOptions: image.DecodingOptions = { editable: request.requestSource === ImageKnifeRequestSource.SRC && request.transformation !== undefined ? true : false, } let imageSource: image.ImageSource = image.createImageSource(resBuf) if (imageSource === undefined){ - ImageKnifeLoader.makeEmptyResult(request,"image.createImageSource failed") + ImageKnifeLoader.makeEmptyResult(request,"image.createImageSource failed", ImageKnifeLoader.assembleError(callBackData, LoadPhase.PHASE_CREATE_SOURCE, LoadPixelMapCode.IMAGE_SOURCE_ERROR_CODE)) return } let size = (await imageSource.getImageInfo()).size + callBackData.imageWidth = size.width; + callBackData.imageHeight = size.height; + timeInfo.decodeStartTime = Date.now(); + await imageSource.createPixelMap(decodingOptions) .then((pixelmap: PixelMap) => { + timeInfo.decodeEndTime = Date.now(); resPixelmap = pixelmap imageSource.release() }).catch((error: BusinessError) => { + timeInfo.decodeEndTime = Date.now(); imageSource.release() - ImageKnifeLoader.makeEmptyResult(request,JSON.stringify(error)) + ImageKnifeLoader.makeEmptyResult(request,JSON.stringify(error), ImageKnifeLoader.assembleError(callBackData, LoadPhase.PHASE_CREATE_PIXEL_MAP, LoadPixelMapCode.IMAGE_DECODE_ERROR_CODE)) return }) if (request.requestSource === ImageKnifeRequestSource.SRC && request.transformation !== undefined && resPixelmap !== undefined) { @@ -107,21 +147,38 @@ export class ImageKnifeLoader { } catch (e) { LogUtil.error("PixelMap setTransferDetached err:"+JSON.stringify(e)) } + + //获取各个pixelMap的大小 + if (resPixelmap && typeof resPixelmap !== "string") { + let decodeImages: Array = []; + let size = (resPixelmap as PixelMap).getImageInfoSync().size; + let decodeImage: DecodeImageInfo = { + contentWidth: size.width, + contentHeight: size.height, + contentSize: (resPixelmap as PixelMap).getPixelBytesNumber() + } + decodeImages.push(decodeImage); + callBackData.decodeImages = decodeImages; + } + let res: RequestJobResult = { pixelMap: resPixelmap, bufferSize: resBuf.byteLength, fileKey: fileKey, size:size, - type:typeValue + type:typeValue, + imageKnifeData:callBackData } emitter.emit(Constants.CALLBACK_EMITTER + request.memoryKey, { data: { "value": res } }) } static async parseSvgImage(resBuf: ArrayBuffer, typeValue: string, fileKey: string, - request: RequestJobRequest) { + request: RequestJobRequest, callBackData: ImageKnifeData) { let resPixelmap: PixelMap | undefined = undefined + let timeInfo: TimeInfo = ImageKnifeLoader.getTimeInfo(callBackData); + let imageSource: image.ImageSource = image.createImageSource(resBuf) if (imageSource === undefined){ - ImageKnifeLoader.makeEmptyResult(request,"image.createImageSource failed") + ImageKnifeLoader.makeEmptyResult(request,"image.createImageSource failed", ImageKnifeLoader.assembleError(callBackData, LoadPhase.PHASE_CREATE_SOURCE, LoadPixelMapCode.IMAGE_SOURCE_ERROR_CODE)) return } @@ -129,6 +186,9 @@ export class ImageKnifeLoader { let scale = size.height / size.width let hValue = Math.round(request.componentHeight); let wValue = Math.round(request.componentWidth); + callBackData.imageWidth = size.width; + callBackData.imageHeight = size.height; + timeInfo.decodeStartTime = Date.now(); let defaultSize: image.Size = { height: vp2px(wValue) * scale, width: vp2px(wValue) @@ -139,6 +199,7 @@ export class ImageKnifeLoader { }; await imageSource.createPixelMap(opts) .then((pixelmap: PixelMap) => { + timeInfo.decodeEndTime = Date.now(); resPixelmap = pixelmap imageSource.release() try { @@ -147,59 +208,88 @@ export class ImageKnifeLoader { LogUtil.error("PixelMap setTransferDetached err:"+JSON.stringify(e)) } }).catch((error: BusinessError) => { + timeInfo.decodeEndTime = Date.now(); imageSource.release() - ImageKnifeLoader.makeEmptyResult(request,JSON.stringify(error)) + ImageKnifeLoader.makeEmptyResult(request,JSON.stringify(error), ImageKnifeLoader.assembleError(callBackData, LoadPhase.PHASE_CREATE_PIXEL_MAP, LoadPixelMapCode.IMAGE_DECODE_ERROR_CODE)) return }) + + //获取各个pixelMap的大小 + if (resPixelmap && typeof resPixelmap !== "string") { + let decodeImages: Array = []; + let decodeImage: DecodeImageInfo = { + contentWidth: defaultSize.width, + contentHeight: defaultSize.height, + contentSize: (resPixelmap as PixelMap).getPixelBytesNumber() + } + decodeImages.push(decodeImage); + } + let res: RequestJobResult = { pixelMap: resPixelmap, bufferSize: resBuf.byteLength, fileKey: fileKey, - type:typeValue + type:typeValue, + imageKnifeData:callBackData } emitter.emit(Constants.CALLBACK_EMITTER + request.memoryKey, { data: { "value": res } }) } static async parseAnimatorImage(resBuf: ArrayBuffer, typeValue: string, - fileKey: string,request: RequestJobRequest) { + fileKey: string,request: RequestJobRequest, callBackData: ImageKnifeData) { + let timeInfo: TimeInfo = ImageKnifeLoader.getTimeInfo(callBackData); let imageSource: image.ImageSource = image.createImageSource(resBuf) if (imageSource === undefined){ - ImageKnifeLoader.makeEmptyResult(request,"image.createImageSource failed") + ImageKnifeLoader.makeEmptyResult(request,"image.createImageSource failed", ImageKnifeLoader.assembleError(callBackData,LoadPhase.PHASE_CREATE_SOURCE,LoadPixelMapCode.IMAGE_SOURCE_ERROR_CODE)) return } let frameCount = await imageSource.getFrameCount() let size = (await imageSource.getImageInfo()).size + callBackData.frameCount = frameCount; + callBackData.imageWidth = size.width; + callBackData.imageHeight = size.height; + imageSource.release() if(frameCount == undefined || frameCount == 1) { } else { + timeInfo.decodeStartTime = Date.now() let base64str = "data:image/" + typeValue + ";base64," + new util.Base64Helper().encodeToStringSync(new Uint8Array(resBuf)) + timeInfo.diskCheckEndTime = Date.now() let res: RequestJobResult = { pixelMap: base64str, bufferSize: resBuf.byteLength, fileKey: fileKey, size:size, - type:typeValue + type:typeValue, + imageKnifeData:callBackData } emitter.emit(Constants.CALLBACK_EMITTER + request.memoryKey, { data: { "value": res } }) return } - ImageKnifeLoader.parseNormalImage(resBuf, typeValue, fileKey, request) + ImageKnifeLoader.parseNormalImage(resBuf, typeValue, fileKey, request, callBackData) } // 为AnimatorComponent解析动图 - static async parseForAnimatorComponent(resBuf: ArrayBuffer, typeValue: string, fileKey: string,request: RequestJobRequest) { + static async parseForAnimatorComponent(resBuf: ArrayBuffer, typeValue: string, fileKey: string,request: RequestJobRequest, callBackData: ImageKnifeData) { + let timeInfo: TimeInfo = ImageKnifeLoader.getTimeInfo(callBackData); + if (typeValue === 'gif' || typeValue === 'webp') { let imageSource: image.ImageSource = image.createImageSource(resBuf); if (imageSource === undefined){ - ImageKnifeLoader.makeEmptyResult(request,"image.createImageSource failed") + ImageKnifeLoader.makeEmptyResult(request,"image.createImageSource failed", ImageKnifeLoader.assembleError(callBackData, LoadPhase.PHASE_CREATE_SOURCE, LoadPixelMapCode.IMAGE_SOURCE_ERROR_CODE)) return } let decodingOptions: image.DecodingOptions = { editable: request.requestSource === ImageKnifeRequestSource.SRC && request.transformation !== undefined ? true : false, } + callBackData.imageWidth = imageSource.getImageInfoSync().size.width; + callBackData.imageHeight = imageSource.getImageInfoSync().size.height; let pixelMapList: Array = [] let delayList: Array = [] + timeInfo.decodeStartTime = Date.now(); + let decodeImages: Array = []; await imageSource.createPixelMapList(decodingOptions).then(async (pixelList: Array) => { + timeInfo.decodeEndTime = Date.now(); //sdk的api接口发生变更:从.getDelayTime() 变为.getDelayTimeList() await imageSource.getDelayTimeList().then(delayTimes => { if (pixelList.length > 0) { @@ -210,26 +300,37 @@ export class ImageKnifeLoader { } else { delayList.push(delayTimes[delayTimes.length - 1]) } + //获取各个pixelMap的大小 + let size = pixelList[i].getImageInfoSync().size + let decodeImage: DecodeImageInfo = { + contentWidth: size.width, + contentHeight: size.height, + contentSize: pixelList[i].getPixelBytesNumber() + } + decodeImages.push(decodeImage); } imageSource.release(); } }) }).catch((error: BusinessError) => { imageSource.release() - ImageKnifeLoader.makeEmptyResult(request,JSON.stringify(error)) + timeInfo.decodeEndTime = Date.now(); + ImageKnifeLoader.makeEmptyResult(request,JSON.stringify(error), ImageKnifeLoader.assembleError(callBackData,LoadPhase.PHASE_CREATE_PIXEL_MAP,LoadPixelMapCode.IMAGE_DECODE_ERROR_CODE)) return }) + callBackData.decodeImages = decodeImages; let res: RequestJobResult = { pixelMap: "", bufferSize: resBuf.byteLength, fileKey: fileKey, type: typeValue, + imageKnifeData:callBackData, pixelMapList, delayList } emitter.emit(Constants.CALLBACK_EMITTER + request.memoryKey, { data: { "value": res } }) } else { - ImageKnifeLoader.makeEmptyResult(request,"ImageKnifeAnimatorComponent组件仅支持动态图") + ImageKnifeLoader.makeEmptyResult(request,"ImageKnifeAnimatorComponent组件仅支持动态图", ImageKnifeLoader.assembleError(callBackData,LoadPhase.PHASE_PARSE_IAMGE,LoadPixelMapCode.IMAGE_FORMAT_ERROR_CODE)) } } static getHeaderObj(request:RequestJobRequest){ @@ -245,23 +346,39 @@ export class ImageKnifeLoader { } return headerObj } - static FileCacheParseImage(request:RequestJobRequest,resBuf:ArrayBuffer,fileKey:string){ + static FileCacheParseImage(request:RequestJobRequest,resBuf:ArrayBuffer,fileKey:string, callBackData: ImageKnifeData){ // 保存文件缓存 if (resBuf !== undefined && request.writeCacheStrategy !== CacheStrategy.Memory) { LogUtil.log("ImageKnife_DataTime_requestJob_saveFileCacheOnlyFile.start:"+request.src) FileCache.saveFileCacheOnlyFile(request.context, fileKey, resBuf , request.fileCacheFolder) LogUtil.log("ImageKnife_DataTime_requestJob_saveFileCacheOnlyFile.end:"+request.src) } - ImageKnifeLoader.parseImage(resBuf,fileKey,request) + ImageKnifeLoader.parseImage(resBuf,fileKey,request, callBackData) } // 获取图片资源 static async getImageArrayBuffer(request: RequestJobRequest, requestList: List | undefined,fileKey:string) { let resBuf: ArrayBuffer | undefined let loadError: string = "" + //定义图片各个阶段错误信息 + let error: ErrorInfo = { code: 0, phase: LoadPhase.PHASE_LOAD } + //定义加载时间点 + let callBackTimeInfo: TimeInfo = {}; + //定义加载信息回调数据 + let callBackData: ImageKnifeData = { + source: "", + imageWidth: 0, + imageHeight: 0, + timeInfo: callBackTimeInfo, + errorInfo: error + }; + // 判断自定义下载 if (request.customGetImage !== undefined && request.requestSource == ImageKnifeRequestSource.SRC && typeof request.src == "string") { // 先从文件缓存获取 + ImageKnifeLoader.assembleError(callBackData,LoadPhase.PHASE_CUSTOM_LOAD) + callBackTimeInfo.diskCheckStartTime = Date.now(); resBuf = FileCache.getFileCacheByFile(request.context, fileKey , request.fileCacheFolder) + callBackTimeInfo.diskCheckEndTime = Date.now(); if (resBuf === undefined) { LogUtil.log("start customGetImage src=" + request.src) const headerObj: Record = ImageKnifeLoader.getHeaderObj(request) @@ -269,17 +386,17 @@ export class ImageKnifeLoader { request.customGetImage(request.context, request.src, headerObj) .then((buffer)=>{ if(buffer != undefined) { - ImageKnifeLoader.FileCacheParseImage(request,buffer,fileKey) + ImageKnifeLoader.FileCacheParseImage(request,buffer,fileKey,callBackData) } else { loadError = "customGetImage loadFail undefined" - ImageKnifeLoader.makeEmptyResult(request,loadError) + ImageKnifeLoader.makeEmptyResult(request,loadError, ImageKnifeLoader.assembleError(callBackData, LoadPhase.PHASE_CUSTOM_LOAD, LoadPixelMapCode.IMAGE_CUSTOM_LOAD_FAILED_CODE)) } }).catch((err:string)=>{ - ImageKnifeLoader.makeEmptyResult(request,err) + ImageKnifeLoader.makeEmptyResult(request,err, ImageKnifeLoader.assembleError(callBackData, LoadPhase.PHASE_CUSTOM_LOAD, LoadPixelMapCode.IMAGE_CUSTOM_LOAD_FAILED_CODE)) }) } catch (e) { loadError = "customGetImage loadFail failed" - ImageKnifeLoader.makeEmptyResult(request,loadError + e) + ImageKnifeLoader.makeEmptyResult(request,loadError + e, ImageKnifeLoader.assembleError(callBackData, LoadPhase.PHASE_CUSTOM_LOAD, LoadPixelMapCode.IMAGE_CUSTOM_LOAD_FAILED_CODE)) } LogUtil.log("end customGetImage src=" + request.src) return @@ -289,12 +406,16 @@ export class ImageKnifeLoader { if (typeof request.src === 'string') { if (request.src.indexOf("http://") == 0 || request.src.indexOf("https://") == 0) { //从网络下载 // 先从文件缓存获取 + ImageKnifeLoader.assembleError(callBackData,LoadPhase.PHASE_NET) + callBackTimeInfo.diskCheckStartTime = Date.now() resBuf = FileCache.getFileCacheByFile(request.context, fileKey , request.fileCacheFolder) + callBackTimeInfo.diskCheckEndTime = Date.now() if (resBuf !== undefined){ LogUtil.log("success get image from filecache for key = " + fileKey + " src = " + request.src) } else if (request.onlyRetrieveFromCache != true) { LogUtil.log("HttpDownloadClient.start:" + request.src) + callBackTimeInfo.netRequestStartTime = Date.now(); let httpRequest = http.createHttp(); let progress: number = 0 let arrayBuffers = new Array() @@ -336,24 +457,29 @@ export class ImageKnifeLoader { }); promise.then((data: number) => { + ImageKnifeLoader.assembleError(callBackData,LoadPhase.PHASE_NET, undefined, data) + callBackTimeInfo.netRequestEndTime = Date.now(); if (data == 200 || data == 206 || data == 204) { resBuf = combineArrayBuffers(arrayBuffers) - ImageKnifeLoader.FileCacheParseImage(request,resBuf,fileKey) + ImageKnifeLoader.FileCacheParseImage(request,resBuf,fileKey, callBackData) } else { loadError = "HttpDownloadClient has error, http code =" + JSON.stringify(data) - ImageKnifeLoader.makeEmptyResult(request,loadError) + ImageKnifeLoader.makeEmptyResult(request,loadError, ImageKnifeLoader.assembleError(callBackData,LoadPhase.PHASE_NET, LoadPixelMapCode.IMAGE_HTTPS_LOAD_FAILED_CODE, data)) } }).catch((err: Error) => { loadError = "HttpDownloadClient download ERROR : err = " + JSON.stringify(err) - ImageKnifeLoader.makeEmptyResult(request,loadError) + callBackTimeInfo.netRequestEndTime = Date.now(); + ImageKnifeLoader.makeEmptyResult(request,loadError, ImageKnifeLoader.assembleError(callBackData,LoadPhase.PHASE_NET, LoadPixelMapCode.IMAGE_HTTPS_LOAD_FAILED_CODE, undefined)) }); LogUtil.log("HttpDownloadClient.end:" + request.src) return } else { + callBackTimeInfo.netRequestEndTime = Date.now(); loadError = 'onlyRetrieveFromCache,do not fetch image src = ' + request.src } } else if (request.src.startsWith('datashare://') || request.src.startsWith('file://')) { + ImageKnifeLoader.assembleError(callBackData,LoadPhase.PHASE_SHARE_FILE) await fs.open(request.src, fs.OpenMode.READ_ONLY).then(async (file) => { await fs.stat(file.fd).then(async (stat) =>{ let buf = new ArrayBuffer(stat.size); @@ -361,15 +487,19 @@ export class ImageKnifeLoader { resBuf = buf; fs.closeSync(file.fd); }).catch((err:BusinessError) => { + ImageKnifeLoader.assembleError(callBackData,LoadPhase.PHASE_SHARE_FILE, LoadPixelMapCode.IMAGE_LOAD_SHARE_FILE_FAILED_CODE) loadError = 'LoadDataShareFileClient fs.read err happened uri=' + request.src + " err.msg=" + err?.message + " err.code=" + err?.code }) }).catch((err:BusinessError) => { + ImageKnifeLoader.assembleError(callBackData,LoadPhase.PHASE_SHARE_FILE, LoadPixelMapCode.IMAGE_LOAD_SHARE_FILE_FAILED_CODE) loadError = 'LoadDataShareFileClient fs.stat err happened uri=' + request.src + " err.msg=" + err?.message + " err.code=" + err?.code }) }).catch((err:BusinessError) => { + ImageKnifeLoader.assembleError(callBackData,LoadPhase.PHASE_SHARE_FILE, LoadPixelMapCode.IMAGE_LOAD_SHARE_FILE_FAILED_CODE) loadError = 'LoadDataShareFileClient fs.open err happened uri=' + request.src + " err.msg=" + err?.message + " err.code=" + err?.code }) } else { //从本地文件获取 + ImageKnifeLoader.assembleError(callBackData,LoadPhase.PHASE_LOCAL_FILE) try { let stat = fs.statSync(request.src); if (stat.size > 0) { @@ -379,6 +509,7 @@ export class ImageKnifeLoader { fs.closeSync(file); } } catch (err) { + ImageKnifeLoader.assembleError(callBackData,LoadPhase.PHASE_LOCAL_FILE, LoadPixelMapCode.IMAGE_LOAD_LOCAL_FILE_FAILED_CODE) loadError = err } } @@ -403,9 +534,10 @@ export class ImageKnifeLoader { } if (resBuf === undefined){ - ImageKnifeLoader.makeEmptyResult(request,loadError) + callBackTimeInfo.requestEndTime = Date.now(); + ImageKnifeLoader.makeEmptyResult(request,loadError ,callBackData) return } - ImageKnifeLoader.parseImage(resBuf,fileKey,request) + ImageKnifeLoader.parseImage(resBuf,fileKey,request, callBackData) } } \ No newline at end of file diff --git a/library/src/main/ets/model/ImageKnifeData.ets b/library/src/main/ets/model/ImageKnifeData.ets index 45b4635..fa96255 100644 --- a/library/src/main/ets/model/ImageKnifeData.ets +++ b/library/src/main/ets/model/ImageKnifeData.ets @@ -20,12 +20,53 @@ import common from '@ohos.app.ability.common'; import { Size } from '@kit.ArkUI' export interface ImageKnifeData { - source: PixelMap | string, - imageWidth: number, + source: PixelMap | string, // url + imageWidth: number, // 原始宽高大小 imageHeight: number, + bufSize?: number, // 图片的字节数 type?:string, imageAnimator?: Array + frameCount ?: number // 帧 + decodeImages?: Array //Image组件或者ImageAnimator组件可以加载一张或者多张 + timeInfo?: TimeInfo // 加载图片的各个时间点 + errorInfo?: ErrorInfo // 错误 } + +/** + * 解码后的图片的size + */ +export interface DecodeImageInfo { + contentWidth ?: number // 解码后宽高 + contentHeight?: number + contentSize ?: number // 大小 +} + +/** + * 加载的错误信息 + */ +export interface ErrorInfo { + phase: string, //图片加载阶段信息,如:网络加载阶段,缓存获取阶段及其解码阶段等 + code: number, + httpCode?: number +} + +/** + * load检查时间点 + */ +export interface TimeInfo { + requestStartTime?: number, + requestEndTime?: number, + requestCancelTime?: number, + memoryCheckStartTime?: number, + memoryCheckEndTime?: number, + diskCheckStartTime?: number, + diskCheckEndTime?: number, + netRequestStartTime?: number, + netRequestEndTime?: number, + decodeStartTime?: number, + decodeEndTime?: number, +} + /** * onComplete成功回调 */ @@ -78,7 +119,8 @@ export interface RequestJobResult { size?:Size, type?: string, pixelMapList?:Array, - delayList?: Array + delayList?: Array, + imageKnifeData?: ImageKnifeData, } /** diff --git a/library/src/main/ets/model/ImageKnifeOption.ets b/library/src/main/ets/model/ImageKnifeOption.ets index b34da66..ffab2ba 100644 --- a/library/src/main/ets/model/ImageKnifeOption.ets +++ b/library/src/main/ets/model/ImageKnifeOption.ets @@ -84,13 +84,13 @@ export class ImageKnifeOption { */ export interface OnLoadCallBack { // 请求开始 - onLoadStart?: () => void; + onLoadStart?: (request?: ImageKnifeRequest) => void; // 请求成功 - onLoadSuccess?: (data: string | PixelMap | undefined, imageKnifeData: ImageKnifeData) => void; + onLoadSuccess?: (data: string | PixelMap | undefined, imageKnifeData: ImageKnifeData, request?: ImageKnifeRequest) => void; // 请求结束 - onLoadFailed?: (err: string,request?: ImageKnifeRequest) => void; + onLoadFailed?: (err: string, request?: ImageKnifeRequest) => void; // 请求取消 - onLoadCancel?: (reason: string) => void; + onLoadCancel?: (reason: string, request?: ImageKnifeRequest) => void; } \ No newline at end of file diff --git a/library/src/main/ets/model/ImageKnifeRequest.ets b/library/src/main/ets/model/ImageKnifeRequest.ets index c63f8be..a9f8649 100644 --- a/library/src/main/ets/model/ImageKnifeRequest.ets +++ b/library/src/main/ets/model/ImageKnifeRequest.ets @@ -14,7 +14,7 @@ */ import { ImageKnifeOption } from './ImageKnifeOption'; import common from '@ohos.app.ability.common'; -import { ImageKnifeRequestSource } from './ImageKnifeData'; +import { ImageKnifeData, ImageKnifeRequestSource } from './ImageKnifeData'; export class ImageKnifeRequest { @@ -27,6 +27,7 @@ export class ImageKnifeRequest { ImageKnifeRequestCallback: ImageKnifeRequestCallback componentVersion: number = 0 headers: Map = new Map() + private imageCallBackData: ImageKnifeData | undefined = undefined; constructor(option: ImageKnifeOption, uIAbilityContext: common.UIAbilityContext, width: number, @@ -53,6 +54,14 @@ export class ImageKnifeRequest { } }) } + + setImageKnifeData(data: ImageKnifeData) { + this.imageCallBackData = data; + } + + getImageKnifeData(): ImageKnifeData | undefined { + return this.imageCallBackData + } } export enum ImageKnifeRequestState { diff --git a/library/src/main/ets/utils/Constants.ets b/library/src/main/ets/utils/Constants.ets index d1ed230..df33374 100644 --- a/library/src/main/ets/utils/Constants.ets +++ b/library/src/main/ets/utils/Constants.ets @@ -15,4 +15,60 @@ export class Constants { public static PROGRESS_EMITTER: string = "progressEmitter" public static CALLBACK_EMITTER: string = "callBackEmitter" +} + +/** + * 图片加载的code + */ +export enum LoadPixelMapCode { + // createImageSource error code + IMAGE_SOURCE_ERROR_CODE = 100001, + // createPixelMap error code + IMAGE_DECODE_ERROR_CODE = 100002, + //ImageKnifeAnimatorComponent组件仅支持动态图 code + IMAGE_FORMAT_ERROR_CODE = 100003, + //load failed code + IMAGE_LOAD_FAILED_CODE = 100004, + //自定义下载失败 code + IMAGE_CUSTOM_LOAD_FAILED_CODE = 100005, + // http请求失败 code + IMAGE_HTTPS_LOAD_FAILED_CODE = 100006, + //设置onlyRetrieveFromCache 导致的加载失败的code + IMAGE_RETRIEVE_CACHE_CODE = 100007, + //加载共享图片失败code + IMAGE_LOAD_SHARE_FILE_FAILED_CODE = 100008, + //加载本地文件图片失败code + IMAGE_LOAD_LOCAL_FILE_FAILED_CODE = 100009, + // 取消请求加载code + IMAGE_LOAD_CANCEL_FAILED_CODE = 1000010, + // 解析图片格式阶段 + IMAGE_PARSE_FORMAT_FAILED_CODE = 1000011 +} + +/** + * 图片加载的各个阶段 + */ +export enum LoadPhase { + // 图片加载阶段 + PHASE_LOAD = "load", + // 网络请求下载阶段 + PHASE_NET = "net", + //获取图片格式阶段 + PHASE_GET_FORMAT = "parse_format", + //自定义下载阶段 customGetImage + PHASE_CUSTOM_LOAD = "customGetImage", + // createPixelMap 阶段 + PHASE_CREATE_SOURCE = "createImageSource", + //createPixelMap 阶段 + PHASE_CREATE_PIXEL_MAP = "createPixelMap", + //请求队列排队阶段 + PHASE_THREAD_QUEUE = "thread_queue", + //图片解析阶段 + PHASE_PARSE_IAMGE = "parseImage", + //加载解析datashare:// 或者file:// 阶段 + PHASE_SHARE_FILE = "datashare_or_file", + //加载解析本地文件阶段 + PHASE_LOCAL_FILE = "load_local_file", + //图片加载解析完成,即将显示的阶段 + PHASE_WILL_SHOW = "will_show", } \ No newline at end of file