From 1a2326c69148e355b88097d77ae49274c996b4df Mon Sep 17 00:00:00 2001 From: tyBrave Date: Fri, 25 Oct 2024 22:47:25 +0800 Subject: [PATCH 1/6] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E5=9B=9E=E8=B0=83=E4=BF=A1=E6=81=AFdemo?= =?UTF-8?q?=E5=8F=8A=E5=85=B6xts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: tyBrave --- entry/src/main/ets/pages/Index.ets | 6 + .../src/main/ets/pages/TestCacheDataPage.ets | 8 +- .../ets/pages/TestImageKnifeCallbackPage.ets | 412 ++++++++++++++++++ .../pages/TestListImageKnifeCallbackPage.ets | 101 +++++ .../ets/pages/TestLoadCancelListenerPage.ets | 8 +- .../main/resources/base/element/string.json | 120 +++++ .../resources/base/profile/main_pages.json | 4 +- .../main/resources/zh_CN/element/string.json | 120 +++++ entry/src/ohosTest/ets/test/List.test.ets | 2 + .../ets/test/loadCallBackData.test.ets | 117 +++++ 10 files changed, 892 insertions(+), 6 deletions(-) create mode 100644 entry/src/main/ets/pages/TestImageKnifeCallbackPage.ets create mode 100644 entry/src/main/ets/pages/TestListImageKnifeCallbackPage.ets create mode 100644 entry/src/ohosTest/ets/test/loadCallBackData.test.ets diff --git a/entry/src/main/ets/pages/Index.ets b/entry/src/main/ets/pages/Index.ets index c6606ed..2b8096a 100644 --- a/entry/src/main/ets/pages/Index.ets +++ b/entry/src/main/ets/pages/Index.ets @@ -197,6 +197,12 @@ struct Index { uri: 'pages/TestLoadCancelListenerPage', }); }) + Button($r('app.string.test_callback')).margin({ top: 10 }).onClick(() => { + router.push({ + uri: 'pages/TestImageKnifeCallbackPage', + + }); + }) } } .width('100%') .height('100%') diff --git a/entry/src/main/ets/pages/TestCacheDataPage.ets b/entry/src/main/ets/pages/TestCacheDataPage.ets index 193dafb..89ac6fc 100644 --- a/entry/src/main/ets/pages/TestCacheDataPage.ets +++ b/entry/src/main/ets/pages/TestCacheDataPage.ets @@ -80,7 +80,7 @@ struct TestCacheDataPage { Text($r('app.string.cur_cache_size', this.markersSizeText, this.currentSize)).fontSize(20).margin({ bottom: 20 }); Button($r('app.string.get_cur_memory_limit')).onClick(() => { - let result = ImageKnife.getInstance().getCacheUpperLimit(CacheStrategy.Memory); + let result = ImageKnife.getInstance().getCacheLimitSize(CacheStrategy.Memory); this.markersLimitText = getContext(this).resourceManager.getStringSync($r('app.string.memory')) if (result) { this.cacheUpLimit = result / (1024 * 1024); @@ -89,7 +89,7 @@ struct TestCacheDataPage { } }).margin({ bottom: 8 }); Button($r('app.string.get_img_number_of_cache')).onClick(() => { - let result = ImageKnife.getInstance().getCurrentPicturesNum(CacheStrategy.Memory); + let result = ImageKnife.getInstance().getCurrentCacheNum(CacheStrategy.Memory); this.markersNumText = getContext(this).resourceManager.getStringSync($r('app.string.memory')) if (result) { this.currentNum = result; @@ -108,7 +108,7 @@ struct TestCacheDataPage { }).margin({ bottom: 8 }); Button($r('app.string.get_cur_disk_limit')).onClick(() => { - let result = ImageKnife.getInstance().getCacheUpperLimit(CacheStrategy.File); + let result = ImageKnife.getInstance().getCacheLimitSize(CacheStrategy.File); this.markersLimitText = getContext(this).resourceManager.getStringSync($r('app.string.disk')) if (result) { this.cacheUpLimit = result / (1024 * 1024); @@ -117,7 +117,7 @@ struct TestCacheDataPage { } }).margin({ bottom: 8 }); Button($r('app.string.get_img_number_of_disk')).onClick(() => { - let result = ImageKnife.getInstance().getCurrentPicturesNum(CacheStrategy.File); + let result = ImageKnife.getInstance().getCurrentCacheNum(CacheStrategy.File); this.markersNumText = getContext(this).resourceManager.getStringSync($r('app.string.disk')) if (result) { this.currentNum = result; diff --git a/entry/src/main/ets/pages/TestImageKnifeCallbackPage.ets b/entry/src/main/ets/pages/TestImageKnifeCallbackPage.ets new file mode 100644 index 0000000..9051649 --- /dev/null +++ b/entry/src/main/ets/pages/TestImageKnifeCallbackPage.ets @@ -0,0 +1,412 @@ +/* + * 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 { ImageKnifeComponent, ImageKnifeData, ImageKnifeOption, ImageKnifeRequest } from '@ohos/imageknife'; +import { router } from '@kit.ArkUI'; + +@Entry +@Component +struct TestImageKnifeCallbackPage { + @State imageKnifeOption: ImageKnifeOption = { + loadSrc: "", + objectFit: ImageFit.Contain, + border: { radius: 50 } + }; + @State currentWidth: number = 200 + @State currentHeight: number = 200 + @State url: string | undefined = "" + @State imageType: string | undefined = "" + @State imageWidth: number | undefined = 0 + @State imageHeight: number | undefined = 0 + @State imageSize: number | undefined = 0 + @State componentWidth: number | undefined = 0 + @State componentHeight: number | undefined = 0 + @State frameNum: number | undefined = 0 + @State decodeSize: string | undefined = "" + @State err_msg: string | undefined = "" + @State err_phase: string | undefined = "" + @State err_code: number | undefined = 0 + @State http_code: number | undefined = 0 + @State reqStartTime: string | undefined = "" + @State reqEndTime: string | undefined = "" + @State reqCancelTime: string | undefined = "" + @State memoryStartTime: string | undefined = "" + @State memoryEndTime: string | undefined = "" + @State diskStartTime: string | undefined = "" + @State diskEndTime: string | undefined = "" + @State netStartTime: string | undefined = "" + @State netEndTime: string | undefined = "" + @State decodeStartTime: string | undefined = "" + @State decodeEndTime: string | undefined = "" + @State renderTime: string | undefined = "" + @State showChild: boolean = true; + + build() { + Column() { + Text($r('app.string.img_url', this.url)).fontSize(14) + Text($r('app.string.img_format', this.imageType)).fontSize(14) + Text($r('app.string.img_master_size', this.imageWidth, this.imageHeight, this.imageSize)).fontSize(14) + Text($r('app.string.componentWH', this.componentWidth, this.componentHeight)).fontSize(14) + Text($r('app.string.img_frame', this.frameNum)).fontSize(14) + Text($r('app.string.img_content_size', this.decodeSize)).fontSize(14) + Text($r('app.string.err_msg', this.err_msg)).fontSize(14) + Text($r('app.string.err_phase', this.err_phase)).fontSize(14) + Text($r('app.string.err_code', this.err_code)).fontSize(14) + Text($r('app.string.http_code', this.http_code)).fontSize(14) + Text($r('app.string.req_start_time', this.reqStartTime)).fontSize(14) + Text($r('app.string.req_end_time', this.reqEndTime)).fontSize(14) + Text($r('app.string.req_cancel_time', this.reqCancelTime)).fontSize(14) + Text($r('app.string.memory_start_time', this.memoryStartTime)).fontSize(14) + Text($r('app.string.memory_end_time', this.memoryEndTime)).fontSize(14) + Text($r('app.string.disk_start_time', this.diskStartTime)).fontSize(14) + Text($r('app.string.disk_end_time', this.diskEndTime)).fontSize(14) + Text($r('app.string.net_start_time', this.netStartTime)).fontSize(14) + Text($r('app.string.net_end_time', this.netEndTime)).fontSize(14) + Text($r('app.string.decode_start_time', this.decodeStartTime)).fontSize(14) + Text($r('app.string.decode_end_time', this.decodeEndTime)).fontSize(14) + Text($r('app.string.render_time', this.renderTime)).fontSize(14) + + Scroll() { + Column() { + + Row() { + Button($r('app.string.Network_images')) + .fontSize(13) + .onClick(() => { + this.destroy(); + this.imageKnifeOption = { + loadSrc: "https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/10/v3/qaEzwkU0QeKb1yehnP2Xig/q7fxAlgMQKup-HUBayRLGQ.jpg", + objectFit: ImageFit.Contain, + onLoadListener: { + onLoadStart: (data) => { + this.analyzeStartCallBackData(data); + }, + onLoadFailed: (res, req) => { + this.analyzeFailedBackData(res, req?.getImageKnifeData()); + }, + onLoadSuccess: (data, imageData, req) => { + this.analyzeSuccessCallBackData(req?.getImageKnifeData()); + }, + onLoadCancel: (res, req) => { + this.analyzeFailedBackData(res, req?.getImageKnifeData()); + } + }, + border: { radius: 50 }, + onComplete: (event) => { + if (event && event.loadingStatus == 0) { + this.renderTime = this.formatDate(Date.now()); + } + } + } + }) + + Button($r('app.string.gif')) + .fontSize(13) + .onClick(() => { + this.destroy(); + this.imageKnifeOption = { + loadSrc: 'https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658', + objectFit: ImageFit.Contain, + onLoadListener: { + onLoadStart: (data) => { + this.analyzeStartCallBackData(data); + }, + onLoadFailed: (res, req) => { + this.analyzeFailedBackData(res, req?.getImageKnifeData()); + }, + onLoadSuccess: (data, imageData, req) => { + this.analyzeSuccessCallBackData(req?.getImageKnifeData()); + }, + onLoadCancel: (res, req) => { + this.analyzeFailedBackData(res, req?.getImageKnifeData()); + } + }, + border: { radius: 50 }, + onComplete: (event) => { + if (event && event.loadingStatus == 0) { + this.renderTime = this.formatDate(Date.now()); + } + } + } + }) + Button($r('app.string.local_pic')) + .fontSize(13) + .onClick(() => { + this.destroy(); + this.imageKnifeOption = { + loadSrc: $r('app.media.pngSample'), + objectFit: ImageFit.Contain, + onLoadListener: { + onLoadStart: (data) => { + this.analyzeStartCallBackData(data); + }, + onLoadFailed: (res, req) => { + this.analyzeFailedBackData(res, req?.getImageKnifeData()); + }, + onLoadSuccess: (data, imageData, req) => { + this.analyzeSuccessCallBackData(req?.getImageKnifeData()); + }, + onLoadCancel: (res, req) => { + this.analyzeFailedBackData(res, req?.getImageKnifeData()); + } + }, + border: { radius: 50 }, + onComplete: (event) => { + if (event && event.loadingStatus == 0) { + this.renderTime = this.formatDate(Date.now()); + } + } + } + }) + } + + Row() { + Button($r('app.string.net_load_failed')) + .fontSize(13) + .onClick(() => { + this.destroy(); + this.imageKnifeOption = { + loadSrc: "https://img-blog.csdn.net/20140514114039140", + objectFit: ImageFit.Contain, + onLoadListener: { + onLoadStart: (data) => { + this.analyzeStartCallBackData(data); + }, + onLoadFailed: (res, req) => { + this.analyzeFailedBackData(res, req?.getImageKnifeData()); + }, + onLoadSuccess: (data, imageData, req) => { + this.analyzeSuccessCallBackData(req?.getImageKnifeData()); + }, + onLoadCancel: (res, req) => { + this.analyzeFailedBackData(res, req?.getImageKnifeData()); + } + }, + border: { radius: 50 }, + onComplete: (event) => { + if (event && event.loadingStatus == 0) { + this.renderTime = this.formatDate(Date.now()); + } + } + } + }) + + Button($r('app.string.local_load_failed')) + .fontSize(13) + .onClick(() => { + this.destroy(); + this.imageKnifeOption = { + loadSrc: 'app.media.xxx', + objectFit: ImageFit.Contain, + onLoadListener: { + onLoadStart: (data) => { + this.analyzeStartCallBackData(data); + }, + onLoadFailed: (res, req) => { + this.analyzeFailedBackData(res, req?.getImageKnifeData()); + }, + onLoadSuccess: (data, imageData, req) => { + this.analyzeSuccessCallBackData(req?.getImageKnifeData()); + }, + onLoadCancel: (res, req) => { + this.analyzeFailedBackData(res, req?.getImageKnifeData()); + } + }, + border: { radius: 50 }, + onComplete: (event) => { + if (event && event.loadingStatus == 0) { + this.renderTime = this.formatDate(Date.now()); + } + } + } + }) + Button($r('app.string.share_load_failed')) + .fontSize(13) + .onClick(() => { + this.destroy(); + this.imageKnifeOption = { + loadSrc: 'datashare://ssas', + objectFit: ImageFit.Contain, + onLoadListener: { + onLoadStart: (data) => { + this.analyzeStartCallBackData(data); + }, + onLoadFailed: (res, req) => { + this.analyzeFailedBackData(res, req?.getImageKnifeData()); + }, + onLoadSuccess: (data, imageData, req) => { + this.analyzeSuccessCallBackData(req?.getImageKnifeData()); + }, + onLoadCancel: (res, req) => { + this.analyzeFailedBackData(res, req?.getImageKnifeData()); + } + }, + border: { radius: 50 }, + onComplete: (event) => { + if (event && event.loadingStatus == 0) { + this.renderTime = this.formatDate(Date.now()); + } + } + } + }) + } + + Button($r('app.string.test_cancel_callback_btn')) + .fontSize(13) + .onClick(() => { + this.destroy(); + this.imageKnifeOption = { + loadSrc: "https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/bf/v3/lSjrRwFcS-ez6jp1ALSQFg/0n7R7XinSPyrYLqDu_1dfw.jpg", + objectFit: ImageFit.Contain, + onLoadListener: { + onLoadStart: (data) => { + this.showChild = false; + this.analyzeStartCallBackData(data); + }, + onLoadFailed: (res, req) => { + this.analyzeFailedBackData(res, req?.getImageKnifeData()); + }, + onLoadSuccess: (data, imageData, req) => { + this.analyzeSuccessCallBackData(req?.getImageKnifeData()); + }, + onLoadCancel: (res, req) => { + this.analyzeFailedBackData(res, req?.getImageKnifeData()); + } + }, + border: { radius: 50 }, + onComplete: (event) => { + if (event && event.loadingStatus == 0) { + this.renderTime = this.formatDate(Date.now()); + } + } + } + }) + + Button($r('app.string.list_pic')) + .fontSize(13) + .onClick(() => { + router.push({ + url: 'pages/TestListImageKnifeCallbackPage', + }); + }) + if (this.showChild) { + ImageKnifeComponent( + { imageKnifeOption: this.imageKnifeOption }) + .height(this.currentHeight) + .width(this.currentWidth) + .margin({ top: 20, bottom: 20 }) + + } + } + .width('100%') + } + }.alignItems(HorizontalAlign.Start) + } + + formatDate(time: number | undefined) { + if (!time) { + return; + } + let date = new Date(time); + const year = date.getFullYear().toString() + let month = (date.getMonth() + 1).toString() + let day = date.getDate().toString() + let hour = date.getHours().toString() + let min = date.getMinutes().toString() + let seconds = date.getSeconds().toString() + let mill = date.getMilliseconds(); + return `${year}-${month}-${day} ${hour}:${min}:${seconds}:${mill}` + } + + analyzeStartCallBackData(req: ImageKnifeRequest | undefined) { + let data = req?.getImageKnifeData(); + if (data) { + if (typeof req?.imageKnifeOption.loadSrc == 'string') { + this.url = req?.imageKnifeOption.loadSrc; + } + this.componentWidth = req?.componentWidth; + this.componentHeight = req?.componentHeight; + this.reqStartTime = this.formatDate(data.timeInfo?.requestStartTime); + this.memoryStartTime = this.formatDate(data.timeInfo?.memoryCheckStartTime); + this.memoryEndTime = this.formatDate(data.timeInfo?.memoryCheckEndTime); + } + } + + analyzeSuccessCallBackData(data: ImageKnifeData | undefined) { + if (data) { + this.imageWidth = data.imageWidth; + this.imageHeight = data.imageHeight; + this.imageSize = data.bufSize; + this.frameNum = data.frameCount; + this.decodeSize = JSON.stringify(data.decodeImages); + this.imageType = data.type; + this.reqEndTime = this.formatDate(data.timeInfo?.requestEndTime); + this.diskStartTime = this.formatDate(data.timeInfo?.diskCheckStartTime); + this.diskEndTime = this.formatDate(data.timeInfo?.diskCheckEndTime); + this.netStartTime = this.formatDate(data.timeInfo?.netRequestStartTime); + this.netEndTime = this.formatDate(data.timeInfo?.netRequestEndTime); + this.decodeStartTime = this.formatDate(data.timeInfo?.diskCheckStartTime); + this.decodeEndTime = this.formatDate(data.timeInfo?.diskCheckEndTime); + } + } + + analyzeFailedBackData(res: string, data: ImageKnifeData | undefined) { + if (data) { + this.err_msg = res; + this.err_phase = data.errorInfo?.phase; + this.err_code = data.errorInfo?.code; + this.http_code = data.errorInfo?.httpCode; + this.reqEndTime = this.formatDate(data.timeInfo?.requestEndTime); + this.diskStartTime = this.formatDate(data.timeInfo?.diskCheckStartTime); + this.diskEndTime = this.formatDate(data.timeInfo?.diskCheckEndTime); + this.netStartTime = this.formatDate(data.timeInfo?.netRequestStartTime); + this.netEndTime = this.formatDate(data.timeInfo?.netRequestEndTime); + this.decodeStartTime = this.formatDate(data.timeInfo?.diskCheckStartTime); + this.decodeEndTime = this.formatDate(data.timeInfo?.diskCheckEndTime); + this.reqCancelTime = this.formatDate(data.timeInfo?.requestCancelTime) + } + } + + destroy() { + this.currentWidth = 200 + this.currentHeight = 200 + this.url = "" + this.imageType = "" + this.imageWidth = 0 + this.imageHeight = 0 + this.imageSize = 0 + this.componentWidth = 0 + this.componentHeight = 0 + this.frameNum = 0 + this.decodeSize = "" + this.err_msg = "" + this.err_phase = "" + this.err_code = 0 + this.http_code = 0 + this.reqStartTime = "" + this.reqEndTime = "" + this.reqCancelTime = "" + this.memoryStartTime = "" + this.memoryEndTime = "" + this.diskStartTime = "" + this.diskEndTime = "" + this.netStartTime = "" + this.netEndTime = "" + this.decodeStartTime = "" + this.decodeEndTime = "" + this.renderTime = "" + this.showChild = true; + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/TestListImageKnifeCallbackPage.ets b/entry/src/main/ets/pages/TestListImageKnifeCallbackPage.ets new file mode 100644 index 0000000..7c190ee --- /dev/null +++ b/entry/src/main/ets/pages/TestListImageKnifeCallbackPage.ets @@ -0,0 +1,101 @@ +/* + * 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 { ImageKnifeComponent} from '@ohos/imageknife'; + +class ArrayElement { + src: string = ""; + w: number = 0; + h: number = 0; +} + +@Entry +@Component +struct TestListImageKnifeCallbackPage { + private wid: number = 200; + private hig: number = 200; + private dataArray: ESObject[] = [ + "https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/56/v3/8MdhfSsCSMKj4sA6okUWrg/5uBx56tLTUO3RYQl-E5JiQ.jpg", + "https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/56/v3/8MdhfSsCSMKj4sA6okUWrg/5uBx56tLTUO3RYQl-E5JiQ.jpg", + "https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658", + "https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/78/v3/qQJpAtRGQe2e_VhbGHDgIw/b3zlit99S6GybD3XdNwqJw.jpg", + "https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/55/v3/5DZ2LLqYSsK85-shqgLveQ/7ZXcyCWNTvOzQP5FFLBGkg.jpg", + "https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/3e/v3/LqRoLI-PRSu9Nqa8KdJ-pQ/dSqskBpSR9eraAMn7NBdqA.jpg", + "https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/25/v3/jgB2ekkTRX-3yTYZalnANQ/xff_x9cbSPqb7fbNwgJa7A.jpg", + "https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/fb/v3/alXwXLHKSyCAIWt_ydgD2g/BCCuu25TREOitQxM7eYOEw.jpg", + "https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/63/v3/qbe6NZkCQyGcITvdWoZBgg/Y-5U1z3GT_yaK8CBD3jkwg.jpg", + "https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/16/v3/fm2tO4TsRH6mv_D_nSSd5w/FscLpLwQQ-KuV7oaprFK2Q.jpg", + "https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/89/v3/UAUvtPHqRD-GWWANsEC57Q/zcRJCQebQ322Aby4jzmwmQ.jpg", + "https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/30/v3/tUUzzx73R4yp8G--lMhuWQ/EBbcu_dLTT-Jj68XAh6mtA.jpg", + "https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/76/v3/EyF6z4FISpCHhae38eEexw/OtyAiu-zSSevNQYvUdtVmA.jpg", + "https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/37/v3/12rH1yiEQmK9wlOOcy5avQ/RzBXiEBRRqOC7LRkwNj6VA.jpg", + "https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/9a/v3/TpRN4AIzRoyUXIqWdKoE0g/ShOnD_tfS46HDbpSWhbCkQ.jpg", + "https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/03/v3/H3X17s8eTdS2w56JgbB5jQ/a45sT-j8Sbe8sSQXTzeYvQ.jpg", + "https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/10/v3/qaEzwkU0QeKb1yehnP2Xig/q7fxAlgMQKup-HUBayRLGQ.jpg", + "https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/96/v3/rMJJoAflTDSWa1z2pHs2wg/8dOqD0GlQBOCL5AvQok9FQ.jpg", + "https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/ed/v3/KMO4D6D2QGuVOCLX4AhOFA/ef51xAaLQuK7BsnuD9abog.jpg", + "https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/d9/v3/FSZH0aTdSqWxeAaxoPvi0g/RqxPxUCXQFiTMBfKTF9kkw.jpg", + "https://hbimg.huabanimg.com/cc6af25f8d782d3cf3122bef4e61571378271145735e9-vEVggB", + 'https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/bf/v3/lSjrRwFcS-ez6jp1ALSQFg/0n7R7XinSPyrYLqDu_1dfw.jpg', + 'https://img-blog.csdn.net/20140514114029140', + 'https://hbimg.huabanimg.com/95a6d37a39aa0b70d48fa18dc7df8309e2e0e8e85571e-x4hhks_fw658/format/webp', + $r('app.media.pngSample'), + $r('app.media.rabbit') + ] + private data: Array = []; + + aboutToAppear(): void { + for (let i = 0; i < this.dataArray.length; i++) { + let element: ArrayElement = { + src: this.dataArray[i], + w: this.wid -(i*5), + h: this.hig -(i*5) + } + this.data.push(element); + } + } + + build() { + List({ space: 3 }) { + ForEach(this.data, (item: ArrayElement) => { + ListItem() { + ImageKnifeComponent({ + imageKnifeOption: + { + loadSrc: item.src, + objectFit: ImageFit.Contain, + onLoadListener: { + onLoadStart: (data) => { + console.log("listCache start:{ url:"+data?.imageKnifeOption.loadSrc +",componentWidth:"+data?.componentWidth+",componentHeight:"+data?.componentHeight+"}," + + JSON.stringify(data?.getImageKnifeData())) + }, + onLoadFailed: (res, req) => { + console.log("listCache onLoadFailed:res:" + res + ";" + JSON.stringify(req?.getImageKnifeData())) + }, + onLoadSuccess: (data, imageData,req) => { + console.log("listCache onLoadSuccess:" + JSON.stringify(req?.getImageKnifeData())) + }, + onLoadCancel: (res, req) => { + console.log("listCache onLoadCancel:res:" + res + ";" + JSON.stringify(req?.getImageKnifeData())) + } + }, + border: { radius: 50 }, + } + }).height(item.w).width(item.h) + }.width('100%') + }, (item: ArrayElement,index) => (item.src+index)) + }.width('100%').height('100%') + } +} \ No newline at end of file diff --git a/entry/src/main/ets/pages/TestLoadCancelListenerPage.ets b/entry/src/main/ets/pages/TestLoadCancelListenerPage.ets index 2d9f75a..794930f 100644 --- a/entry/src/main/ets/pages/TestLoadCancelListenerPage.ets +++ b/entry/src/main/ets/pages/TestLoadCancelListenerPage.ets @@ -13,7 +13,7 @@ * limitations under the License. */ -import { ImageKnifeComponent, ImageKnifeOption } from '@ohos/imageknife'; +import { ImageKnife, ImageKnifeComponent, ImageKnifeOption } from '@ohos/imageknife'; @Entry @@ -35,6 +35,7 @@ struct TestLoadCancelListenerPage { Button($r('app.string.rm_component_of_net')) .margin(20) .onClick(() => { + this.clearCache(); this.ImageKnifeOption = { loadSrc: "https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/76/v3/EyF6z4FISpCHhae38eEexw/OtyAiu-zSSevNQYvUdtVmA.jpg", objectFit: ImageFit.Contain, @@ -64,6 +65,7 @@ struct TestLoadCancelListenerPage { Button($r('app.string.rm_component_of_local')) .margin(20) .onClick(() => { + this.clearCache(); this.ImageKnifeOption = { loadSrc: $r('app.media.loading'), objectFit: ImageFit.Contain, @@ -92,4 +94,8 @@ struct TestLoadCancelListenerPage { .height('100%') .width('100%') } + + clearCache(){ + ImageKnife.getInstance().removeAllMemoryCache(); + } } \ No newline at end of file diff --git a/entry/src/main/resources/base/element/string.json b/entry/src/main/resources/base/element/string.json index f4950aa..5bb2d46 100644 --- a/entry/src/main/resources/base/element/string.json +++ b/entry/src/main/resources/base/element/string.json @@ -511,6 +511,126 @@ { "name": "request_concurrency", "value": "Set request concurrency" + }, + { + "name": "test_callback", + "value": "test callback data of load pic" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "local_pic", + "value": "local picture" + }, + { + "name": "share_pic", + "value": "share picture" + }, + { + "name": "net_load_failed", + "value": "netWork load failed" + }, + { + "name": "local_load_failed", + "value": "local load failed" + }, + { + "name": "share_load_failed", + "value": "shared load failed" + }, + { + "name": "list_pic", + "value": "load picture list" + }, + { + "name": "img_url", + "value": "url of the service setting:%s" + }, + { + "name": "img_format", + "value": "picture format:%s" + }, + { + "name": "img_master_size", + "value": "the original width and height of the picture-w:%d ,h:%d, size:%d" + }, + { + "name": "componentWH", + "value": "component width and height-w:%d ,h:%d " + }, + { + "name": "img_frame", + "value": "the number of frames of the picture:%d " + }, + { + "name": "img_content_size", + "value": "picture decoded width and height size:%s " + }, + { + "name": "err_msg", + "value": "error message:%s " + }, + { + "name": "err_phase", + "value": "error phase:%s " + }, + { + "name": "err_code", + "value": "error code:%d " + }, + { + "name": "http_code", + "value": "http code:%d " + }, + { + "name": "req_start_time", + "value": "image request start time point:%s " + }, + { + "name": "req_end_time", + "value": "the point in time at which the image request ends:%s " + }, + { + "name": "req_cancel_time", + "value": "the point at which the image request is cancelled:%s " + }, + { + "name": "memory_start_time", + "value": "start checking the memory cache point in time:%s " + }, + { + "name": "memory_end_time", + "value": "end Check the memory cache time point:%s " + }, + { + "name": "disk_start_time", + "value": "start checking the disk cache point in time:%s " + }, + { + "name": "disk_end_time", + "value": "end Check the disk cache time point:%s " + }, + { + "name": "net_start_time", + "value": "start time of the network request:%s " + }, + { + "name": "net_end_time", + "value": "end time of the network request:%s:%s " + }, + { + "name": "decode_start_time", + "value": "decoding start time point:%s " + }, + { + "name": "decode_end_time", + "value": "decoding end time point:%s " + }, + { + "name": "render_time", + "value": "render successful time:%s " } ] } \ No newline at end of file diff --git a/entry/src/main/resources/base/profile/main_pages.json b/entry/src/main/resources/base/profile/main_pages.json index 0b15726..f181a32 100644 --- a/entry/src/main/resources/base/profile/main_pages.json +++ b/entry/src/main/resources/base/profile/main_pages.json @@ -36,6 +36,8 @@ "pages/SetMaxRequestPage", "pages/MaxRequest1", "pages/MaxRequest2", - "pages/MaxRequest3" + "pages/MaxRequest3", + "pages/TestImageKnifeCallbackPage", + "pages/TestListImageKnifeCallbackPage" ] } \ No newline at end of file diff --git a/entry/src/main/resources/zh_CN/element/string.json b/entry/src/main/resources/zh_CN/element/string.json index 01fa780..8d8e25f 100644 --- a/entry/src/main/resources/zh_CN/element/string.json +++ b/entry/src/main/resources/zh_CN/element/string.json @@ -507,6 +507,126 @@ { "name": "request_concurrency", "value": "设置请求并发度" + }, + { + "name": "test_callback", + "value": "测试图片加载回调数据" + }, + { + "name": "gif", + "value": "gif" + }, + { + "name": "local_pic", + "value": "本地图片" + }, + { + "name": "share_pic", + "value": "共享图片" + }, + { + "name": "net_load_failed", + "value": "网络加载失败" + }, + { + "name": "local_load_failed", + "value": "本地加载失败" + }, + { + "name": "share_load_failed", + "value": "共享图片加载失败" + }, + { + "name": "list_pic", + "value": "加载图片列表" + }, + { + "name": "img_url", + "value": "业务设置的url:%s" + }, + { + "name": "img_format", + "value": "图片的格式:%s" + }, + { + "name": "img_master_size", + "value": "图片的原始宽高大小-宽:%d ,高:%d, 大小:%d" + }, + { + "name": "componentWH", + "value": "component的宽高-宽:%d ,高:%d " + }, + { + "name": "img_frame", + "value": "图片的帧数:%d " + }, + { + "name": "img_content_size", + "value": "图片解码后宽高大小:%s " + }, + { + "name": "err_msg", + "value": "错误信息:%s " + }, + { + "name": "err_phase", + "value": "发生错误阶段:%s " + }, + { + "name": "err_code", + "value": "错误code:%d " + }, + { + "name": "http_code", + "value": "网络请求code:%d " + }, + { + "name": "req_start_time", + "value": "图片的请求开始时间点:%s " + }, + { + "name": "req_end_time", + "value": "图片请求结束的时间点:%s " + }, + { + "name": "req_cancel_time", + "value": "图片请求取消的时间点:%s " + }, + { + "name": "memory_start_time", + "value": "开始检查内存缓存时间点:%s " + }, + { + "name": "memory_end_time", + "value": "结束检查内存缓存时间点:%s " + }, + { + "name": "disk_start_time", + "value": "开始检查磁盘缓存时间点:%s " + }, + { + "name": "disk_end_time", + "value": "结束检查磁盘缓存时间点:%s " + }, + { + "name": "net_start_time", + "value": "网络请求开始时间点:%s " + }, + { + "name": "net_end_time", + "value": "网络请求结束时间点:%s " + }, + { + "name": "decode_start_time", + "value": "解码开始时间点:%s " + }, + { + "name": "decode_end_time", + "value": "解码结束时间点:%s " + }, + { + "name": "render_time", + "value": "渲染成功的时间:%s " } ] } \ No newline at end of file diff --git a/entry/src/ohosTest/ets/test/List.test.ets b/entry/src/ohosTest/ets/test/List.test.ets index 1b17e5f..6c389ee 100644 --- a/entry/src/ohosTest/ets/test/List.test.ets +++ b/entry/src/ohosTest/ets/test/List.test.ets @@ -19,6 +19,7 @@ import MemoryLruCacheTest from './MemoryLruCache.test'; import ImageKnifeTest from './ImageKnife.test'; import Transform from './transform.test'; import imageFormatAndSize from './imageFormatAndSize.test' +import loadCallBackData from './loadCallBackData.test' export default function testsuite() { MemoryLruCacheTest(); @@ -28,4 +29,5 @@ export default function testsuite() { ImageKnifeTest(); Transform(); imageFormatAndSize(); + loadCallBackData(); } \ No newline at end of file diff --git a/entry/src/ohosTest/ets/test/loadCallBackData.test.ets b/entry/src/ohosTest/ets/test/loadCallBackData.test.ets new file mode 100644 index 0000000..256159b --- /dev/null +++ b/entry/src/ohosTest/ets/test/loadCallBackData.test.ets @@ -0,0 +1,117 @@ +/* + * 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, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; +import { + ImageKnifeOption, + ImageKnife, + ImageKnifeRequest, + ImageKnifeRequestSource, + CacheStrategy +} from "@ohos/imageknife" +import { common } from '@kit.AbilityKit'; + +export default function loadCallBackData() { + describe('loadCallBackData', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }); + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }); + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }); + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }); + it('startAndSuccess-CallBack', 0, async () => { + let startCallBack: ESObject = undefined; + let successCallBack: ESObject = undefined; + let url: string = + "https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/63/v3/qbe6NZkCQyGcITvdWoZBgg/Y-5U1z3GT_yaK8CBD3jkwg.jpg" + let imageKnifeOption: ImageKnifeOption = { + loadSrc: url, + } + await new Promise((resolve, reject) => { + imageKnifeOption.onLoadListener = { + onLoadStart: (data) => { + startCallBack = data?.getImageKnifeData(); + }, + onLoadSuccess: (data, imageknifeData,req) => { + successCallBack = req?.getImageKnifeData(); + resolve("") + }, + onLoadFailed(err) { + reject(err) + } + } + let request = new ImageKnifeRequest( + imageKnifeOption, + imageKnifeOption.context !== undefined ? imageKnifeOption.context : getContext() as common.UIAbilityContext, + 0, + 0, + 0, + { + showPixelMap(version: number, pixelMap: PixelMap | string) { + } + } + ) + ImageKnife.getInstance().execute(request); + }) + expect(startCallBack != undefined).assertTrue(); + expect(successCallBack != undefined).assertTrue(); + }); + it('failed-CallBack', 0, async () => { + let failedCallBack: ESObject = undefined; + let url: string = + "https://contentcenter-drcn.dbankcdn.cn/pub_1/DevEcoSpace_1_900_9/163/v3/qbe6NZkCQyGcITvdWoZBgg/Y-5U1z3GT_yaK8CBD3jkwg.jpg" + let imageKnifeOption: ImageKnifeOption = { + loadSrc: url, + } + await new Promise((resolve, reject) => { + imageKnifeOption.onLoadListener = { + onLoadStart: (data) => { + }, + onLoadSuccess: (data, imageknifeData) => { + }, + onLoadFailed(res,req) { + failedCallBack = req?.getImageKnifeData(); + resolve(res) + } + } + let request = new ImageKnifeRequest( + imageKnifeOption, + imageKnifeOption.context !== undefined ? imageKnifeOption.context : getContext() as common.UIAbilityContext, + 0, + 0, + 0, + { + showPixelMap(version: number, pixelMap: PixelMap | string) { + } + } + ) + ImageKnife.getInstance().execute(request); + }) + expect(failedCallBack != undefined).assertTrue(); + }); + }); +} \ No newline at end of file From b57194cf4cebb1af85475be371b1f30a752ba389 Mon Sep 17 00:00:00 2001 From: tyBrave Date: Fri, 25 Oct 2024 22:48:53 +0800 Subject: [PATCH 2/6] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E5=9B=9E=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 From c5a861f4fe1a248d8c7aa06654b71aafb66dff38 Mon Sep 17 00:00:00 2001 From: tyBrave Date: Fri, 25 Oct 2024 22:51:18 +0800 Subject: [PATCH 3/6] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E5=9B=9E=E8=B0=83=E4=BF=A1=E6=81=AF=E7=9A=84README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: tyBrave --- CHANGELOG.md | 4 ++++ README.md | 36 +++++++++++++++++++++++++++++++++++- README_zh.md | 36 +++++++++++++++++++++++++++++++++++- library/oh-package.json5 | 2 +- 4 files changed, 75 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcaf96b..cfe310e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 3.2.0-rc.1 +- Added callback information for image loading +- Added the interface for obtaining the upper limit and size of the current cache and the number of images corresponding to the current cache + ## 3.2.0-rc.0 - Rollback the old version V1 decorator. V2 decorator will be provided in version 4.x - The sub-thread network request is changed to asynchronous, thereby increasing the number of concurrent sub-thread network requests diff --git a/README.md b/README.md index c1a3ab4..16f35bb 100644 --- a/README.md +++ b/README.md @@ -264,6 +264,36 @@ ImageKnifeAnimatorComponent({ },animatorOption:this.animatorOption }).width(300).height(300).backgroundColor(Color.Orange).margin({top:30}) ``` + +#### 11.Load the image callback information data +``` +ImageKnifeComponent({ ImageKnifeOption: = new ImageKnifeOption({ + loadSrc: $r('app.media.pngSample'), + objectFit: ImageFit.Contain, + onLoadListener: { + onLoadStart: (req) => { + let startCallBackData = JSON.stringify(req?.getImageKnifeData()); + }, + onLoadFailed: (res, req) => { + let failedBackData = res + ";" + JSON.stringify(req?.getImageKnifeData()); + }, + onLoadSuccess: (data, imageData, req) => { + let successBackData = JSON.stringify(req?.getImageKnifeData()); + }, + onLoadCancel: (res, req) => { + let cancelBackData = res + ";" + JSON.stringify(req?.getImageKnifeData()); + } + }, + border: { radius: 50 }, + onComplete: (event) => { + if (event && event.loadingStatus == 0) { + let render_success = JSON.stringify(Date.now()) + } + } + }) +}).width(100).height(100) +``` + #### Reuse Scenario Clear the component content in the **aboutToRecycle** lifecycle and trigger image loading through watch observeration. ## Available APIs @@ -307,7 +337,7 @@ Clear the component content in the **aboutToRecycle** lifecycle and trigger imag | transformation | PixelMapTransformation | Image transformation. Optional. | | drawingColorFilter | ColorFilter | Drawing color filter. Optional. | | onComplete | (event:EventImage \| undefined)=>void | Callback for image loading completion. Optional. | -| onLoadListener | onLoadStart:()=>void,onLoadSuccess:(data:string\|Pixelmap)=>void | Callback for image loading events. Optional. | +| onLoadListener | onLoadStart?: (req?: ImageKnifeRequest) => void,onLoadSuccess?: (data: string \| PixelMap \| undefined, imageData: ImageKnifeData, req?: ImageKnifeRequest) => void,onLoadFailed?: (err: string, req?: ImageKnifeRequest) => void,onLoadCancel?: (res: string, req?: ImageKnifeRequest) => void | Callback for image loading events. Optional. | ### ImageKnife @@ -325,6 +355,10 @@ Clear the component content in the **aboutToRecycle** lifecycle and trigger imag | putCacheImage | url: string, pixelMap: PixelMap, cacheType: CacheStrategy = CacheStrategy.Default, signature?: string | Writes to the memory disk cache. | | removeMemoryCache | url: string | Removes an entry from the memory cache. | | removeFileCache | url: string | Removes an entry from the file cache. | +| getCacheLimitSize | cacheType?: CacheStrategy | Gets the upper limit size of the specified cache | +| getCurrentCacheNum | cacheType?: CacheStrategy | Gets the number of images currently cached in the specified cache | +| getCurrentCacheSize | cacheType?: CacheStrategy | Gets the current size of the specified cache | + ### Graphics tRansformation Types (GPUImage Dependency Required) | Type | Description | diff --git a/README_zh.md b/README_zh.md index 53bed8f..a01aac5 100644 --- a/README_zh.md +++ b/README_zh.md @@ -264,6 +264,35 @@ ImageKnifeAnimatorComponent({ },animatorOption:this.animatorOption }).width(300).height(300).backgroundColor(Color.Orange).margin({top:30}) ``` +#### 11.加载图片回调信息数据 示例 +``` +ImageKnifeComponent({ ImageKnifeOption: = new ImageKnifeOption({ + loadSrc: $r('app.media.pngSample'), + objectFit: ImageFit.Contain, + onLoadListener: { + onLoadStart: (req) => { + let startCallBackData = JSON.stringify(req?.getImageKnifeData()); + }, + onLoadFailed: (res, req) => { + let failedBackData = res + ";" + JSON.stringify(req?.getImageKnifeData()); + }, + onLoadSuccess: (data, imageData, req) => { + let successBackData = JSON.stringify(req?.getImageKnifeData()); + }, + onLoadCancel: (res, req) => { + let cancelBackData = res + ";" + JSON.stringify(req?.getImageKnifeData()); + } + }, + border: { radius: 50 }, + onComplete: (event) => { + if (event && event.loadingStatus == 0) { + let render_success = JSON.stringify(Date.now()) + } + } + }) +}).width(100).height(100) +``` + #### 复用场景 在aboutToRecycle生命周期清空组件内容;通过watch监听触发图片的加载。 ## 接口说明 @@ -307,7 +336,7 @@ ImageKnifeAnimatorComponent({ | transformation | PixelMapTransformation | 图片变换(可选) | | drawingColorFilter | ColorFilter | drawing.ColorFilter | 图片变换(可选) | | onComplete | (event:EventImage | undefined) => voi | 颜色滤镜效果(可选) | -| onLoadListener | onLoadStart: () => void,onLoadSuccess: (data: string、PixelMap、undefined) => void,onLoadFailed: (err: string) => void | 监听图片加载成功与失败 | +| onLoadListener | onLoadStart?: (req?: ImageKnifeRequest) => void,onLoadSuccess?: (data: string \| PixelMap \| undefined, imageData: ImageKnifeData, req?: ImageKnifeRequest) => void,onLoadFailed?: (err: string, req?: ImageKnifeRequest) => void,onLoadCancel?: (res: string, req?: ImageKnifeRequest) => void | 监听图片加载成功与失败 | ### ImageKnife接口 @@ -328,6 +357,11 @@ ImageKnifeAnimatorComponent({ | putCacheImage | url: string, pixelMap: PixelMap, cacheType: CacheStrategy = CacheStrategy.Default, signature?: string | 写入内存磁盘缓存 | | removeMemoryCache | url: string | ImageKnifeOption | 清理指定内存缓存 | | removeFileCache | url: string | ImageKnifeOption | 清理指定磁盘缓存 | +| getCacheLimitSize | cacheType?: CacheStrategy | 获取指定缓存的上限大小 | +| getCurrentCacheNum | cacheType?: CacheStrategy | 获取指定缓存的当前缓存图片个数 | +| getCurrentCacheSize | cacheType?: CacheStrategy | 获取指定缓存的当前大小 | + + ### 图形变换类型(需要为GPUImage添加依赖项) | 类型 | 相关描述 | diff --git a/library/oh-package.json5 b/library/oh-package.json5 index 4d5208b..5b0a79b 100644 --- a/library/oh-package.json5 +++ b/library/oh-package.json5 @@ -14,7 +14,7 @@ "main": "index.ets", "repository": "https://gitee.com/openharmony-tpc/ImageKnife", "type": "module", - "version": "3.2.0-rc.0", + "version": "3.2.0-rc.1", "dependencies": { "@ohos/gpu_transform": "^1.0.2" }, From 5f2e80400abf96bd2b516cdf4a551c842c43d143 Mon Sep 17 00:00:00 2001 From: tyBrave Date: Tue, 29 Oct 2024 15:17:47 +0800 Subject: [PATCH 4/6] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E9=92=88=E5=AF=B9?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E5=9B=9E=E8=B0=83=E4=BF=A1=E6=81=AF=E9=9C=80?= =?UTF-8?q?=E6=B1=82=E7=9A=84=E5=AE=A1=E6=A0=B8=E6=84=8F=E8=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: tyBrave --- README.md | 38 +++++-------------- README_zh.md | 36 ++++-------------- .../src/main/ets/pages/TestCacheDataPage.ets | 2 +- .../ets/pages/TestImageKnifeCallbackPage.ets | 2 +- .../pages/TestListImageKnifeCallbackPage.ets | 2 +- .../ets/pages/TestLoadCancelListenerPage.ets | 2 +- library/src/main/ets/ImageKnifeDispatcher.ets | 2 +- 7 files changed, 22 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index 16f35bb..fb65c0e 100644 --- a/README.md +++ b/README.md @@ -265,35 +265,6 @@ ImageKnifeAnimatorComponent({ }).width(300).height(300).backgroundColor(Color.Orange).margin({top:30}) ``` -#### 11.Load the image callback information data -``` -ImageKnifeComponent({ ImageKnifeOption: = new ImageKnifeOption({ - loadSrc: $r('app.media.pngSample'), - objectFit: ImageFit.Contain, - onLoadListener: { - onLoadStart: (req) => { - let startCallBackData = JSON.stringify(req?.getImageKnifeData()); - }, - onLoadFailed: (res, req) => { - let failedBackData = res + ";" + JSON.stringify(req?.getImageKnifeData()); - }, - onLoadSuccess: (data, imageData, req) => { - let successBackData = JSON.stringify(req?.getImageKnifeData()); - }, - onLoadCancel: (res, req) => { - let cancelBackData = res + ";" + JSON.stringify(req?.getImageKnifeData()); - } - }, - border: { radius: 50 }, - onComplete: (event) => { - if (event && event.loadingStatus == 0) { - let render_success = JSON.stringify(Date.now()) - } - } - }) -}).width(100).height(100) -``` - #### Reuse Scenario Clear the component content in the **aboutToRecycle** lifecycle and trigger image loading through watch observeration. ## Available APIs @@ -359,6 +330,15 @@ Clear the component content in the **aboutToRecycle** lifecycle and trigger imag | getCurrentCacheNum | cacheType?: CacheStrategy | Gets the number of images currently cached in the specified cache | | getCurrentCacheSize | cacheType?: CacheStrategy | Gets the current size of the specified cache | + +### Description of the callback interface +| Callback interface | Callback field | Description of callback data | +|------------------------------|-------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| onLoadStart | req: ImageKnifeRequest | The req return field contains information about the image request, such as the url of the image and the width and height of its components, while ImageKnifeRequest contains ImageKnifeData, which contains the start of the request and the point at which it checks the memory cache | +| onLoadSuccess | data: string \| PixelMap \| undefined, imageData: ImageKnifeData, req?: ImageKnifeRequest | data: The result data is loaded successfully. imageData: information stored in the cache of the image, req: information of the image request, and ImageKnifeData, including the original size of the image in the request, image decoding size, format, picture frame, request end time, disk check time, network request start and end, image decoding start and end and other time points | +| onLoadFailed | err: string, req?: ImageKnifeRequest | err: indicates error information. req: Information of image request. Meanwhile, ImageKnifeData contains the error information of the request (ErrorInfo, TimeInfo). ErrorInfo contains the error phase, error code and the error code of network request. TimeInfo contains the end time of the request, the disk check time, the start and end of the network request, and the start and end of the image decoding | +| onLoadCancel | reason: string, req?: ImageKnifeRequest | reason: indicates the reason for canceling the callback. req: Information of image request. Meanwhile, ImageKnifeData contains the error information of the request (ErrorInfo, TimeInfo). ErrorInfo contains the error phase, error code and the error code of network request. TimeInfo contains the end time of the request, the disk check time, the start and end of the network request, the start and end of the image decoding, and the request cancellation | + ### Graphics tRansformation Types (GPUImage Dependency Required) | Type | Description | diff --git a/README_zh.md b/README_zh.md index a01aac5..86cd27a 100644 --- a/README_zh.md +++ b/README_zh.md @@ -264,34 +264,6 @@ ImageKnifeAnimatorComponent({ },animatorOption:this.animatorOption }).width(300).height(300).backgroundColor(Color.Orange).margin({top:30}) ``` -#### 11.加载图片回调信息数据 示例 -``` -ImageKnifeComponent({ ImageKnifeOption: = new ImageKnifeOption({ - loadSrc: $r('app.media.pngSample'), - objectFit: ImageFit.Contain, - onLoadListener: { - onLoadStart: (req) => { - let startCallBackData = JSON.stringify(req?.getImageKnifeData()); - }, - onLoadFailed: (res, req) => { - let failedBackData = res + ";" + JSON.stringify(req?.getImageKnifeData()); - }, - onLoadSuccess: (data, imageData, req) => { - let successBackData = JSON.stringify(req?.getImageKnifeData()); - }, - onLoadCancel: (res, req) => { - let cancelBackData = res + ";" + JSON.stringify(req?.getImageKnifeData()); - } - }, - border: { radius: 50 }, - onComplete: (event) => { - if (event && event.loadingStatus == 0) { - let render_success = JSON.stringify(Date.now()) - } - } - }) -}).width(100).height(100) -``` #### 复用场景 在aboutToRecycle生命周期清空组件内容;通过watch监听触发图片的加载。 @@ -360,7 +332,15 @@ ImageKnifeComponent({ ImageKnifeOption: = new ImageKnifeOption({ | getCacheLimitSize | cacheType?: CacheStrategy | 获取指定缓存的上限大小 | | getCurrentCacheNum | cacheType?: CacheStrategy | 获取指定缓存的当前缓存图片个数 | | getCurrentCacheSize | cacheType?: CacheStrategy | 获取指定缓存的当前大小 | +| getCurrentCacheSize | cacheType?: CacheStrategy | 获取指定缓存的当前大小 | +### 回调接口字段说明 +| 回调接口 | 回调字段 | 回调描述 | +|----------------|-------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| onLoadStart | req: ImageKnifeRequest | req返回字段中包含了图片请求的信息,如图片的url及其组件的宽高,同时ImageKnifeRequest包含了ImageKnifeData,其中包含此次请求的开始及其检查内存缓存的时间点 | +| onLoadSuccess | data: string \| PixelMap \| undefined, imageData: ImageKnifeData, req?: ImageKnifeRequest | data:加载成功的结果数据;imageData:图片的存入缓存中的信息 ,req:图片请求的信息,同时其中的ImageKnifeData,包含此次请求中图片的原始大小、图片的解码大小、格式、图片帧、请求结束时间、磁盘检查时间、网络请求开始结束、图片解码开始结束等时间点 | +| onLoadFailed | err: string, req?: ImageKnifeRequest | err:错误信息描述;req:图片请求的信息,同时其中的ImageKnifeData,包含此次请求错误信息(ErrorInfo,TimeInfo),ErrorInfo其中包含了,错误阶段、错误码及其网络请求的错误码;TimeInfo中包含请求结束时间、磁盘检查时间、网络请求开始结束、图片解码开始结束等时间点 | +| onLoadCancel | reason: string, req?: ImageKnifeRequest | reason:取消回调原因;req:图片请求的信息,同时其中的ImageKnifeData,包含此次请求错误信息(ErrorInfo,TimeInfo),ErrorInfo其中包含了,错误阶段、错误码及其网络请求的错误码;TimeInfo中包含请求结束时间、磁盘检查时间、网络请求开始结束、图片解码开始结束及其请求取消等时间点 | ### 图形变换类型(需要为GPUImage添加依赖项) diff --git a/entry/src/main/ets/pages/TestCacheDataPage.ets b/entry/src/main/ets/pages/TestCacheDataPage.ets index 89ac6fc..b7b75c2 100644 --- a/entry/src/main/ets/pages/TestCacheDataPage.ets +++ b/entry/src/main/ets/pages/TestCacheDataPage.ets @@ -13,7 +13,7 @@ * limitations under the License. */ -import { ImageKnife, CacheStrategy, ImageKnifeComponent, ImageKnifeOption } from '@ohos/imageknife'; +import { ImageKnife, CacheStrategy, ImageKnifeComponent, ImageKnifeOption } from '@ohos/libraryimageknife'; @Entry @Component diff --git a/entry/src/main/ets/pages/TestImageKnifeCallbackPage.ets b/entry/src/main/ets/pages/TestImageKnifeCallbackPage.ets index 9051649..9efc1fb 100644 --- a/entry/src/main/ets/pages/TestImageKnifeCallbackPage.ets +++ b/entry/src/main/ets/pages/TestImageKnifeCallbackPage.ets @@ -13,7 +13,7 @@ * limitations under the License. */ -import { ImageKnifeComponent, ImageKnifeData, ImageKnifeOption, ImageKnifeRequest } from '@ohos/imageknife'; +import { ImageKnifeComponent, ImageKnifeData, ImageKnifeOption, ImageKnifeRequest } from '@ohos/libraryimageknife'; import { router } from '@kit.ArkUI'; @Entry diff --git a/entry/src/main/ets/pages/TestListImageKnifeCallbackPage.ets b/entry/src/main/ets/pages/TestListImageKnifeCallbackPage.ets index 7c190ee..fc004e3 100644 --- a/entry/src/main/ets/pages/TestListImageKnifeCallbackPage.ets +++ b/entry/src/main/ets/pages/TestListImageKnifeCallbackPage.ets @@ -13,7 +13,7 @@ * limitations under the License. */ -import { ImageKnifeComponent} from '@ohos/imageknife'; +import { ImageKnifeComponent} from '@ohos/libraryimageknife'; class ArrayElement { src: string = ""; diff --git a/entry/src/main/ets/pages/TestLoadCancelListenerPage.ets b/entry/src/main/ets/pages/TestLoadCancelListenerPage.ets index 794930f..4508198 100644 --- a/entry/src/main/ets/pages/TestLoadCancelListenerPage.ets +++ b/entry/src/main/ets/pages/TestLoadCancelListenerPage.ets @@ -13,7 +13,7 @@ * limitations under the License. */ -import { ImageKnife, ImageKnifeComponent, ImageKnifeOption } from '@ohos/imageknife'; +import { ImageKnife, ImageKnifeComponent, ImageKnifeOption } from '@ohos/libraryimageknife'; @Entry diff --git a/library/src/main/ets/ImageKnifeDispatcher.ets b/library/src/main/ets/ImageKnifeDispatcher.ets index 8de5152..7389684 100644 --- a/library/src/main/ets/ImageKnifeDispatcher.ets +++ b/library/src/main/ets/ImageKnifeDispatcher.ets @@ -436,7 +436,7 @@ export class ImageKnifeDispatcher { // 回调请求成功 this.assembleImageKnifeData(requestWithSource.request.getImageKnifeData(), imageKnifeData,requestWithSource.request); requestWithSource.request.imageKnifeOption.onLoadListener.onLoadSuccess(imageKnifeData.source, - imageKnifeData, requestWithSource.request); + saveCacheImageData, requestWithSource.request); LogUtil.log("ImageKnife_DataTime_getAndShowImage_onLoadSuccess:"+currentRequest.imageKnifeOption.loadSrc) } } else if (requestWithSource.source == ImageKnifeRequestSource.ERROR_HOLDER) { From 7210d5060b62bea92cf2a818da116fbb15881246 Mon Sep 17 00:00:00 2001 From: tyBrave Date: Tue, 29 Oct 2024 16:23:38 +0800 Subject: [PATCH 5/6] =?UTF-8?q?=E6=9B=B4=E6=96=B0README=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: tyBrave --- README.md | 49 +++++++++++++++++++++++++++++++++++++++---------- README_zh.md | 32 +++++++++++++++++++++++++++++++- 2 files changed, 70 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index fb65c0e..5641fc7 100644 --- a/README.md +++ b/README.md @@ -265,6 +265,35 @@ ImageKnifeAnimatorComponent({ }).width(300).height(300).backgroundColor(Color.Orange).margin({top:30}) ``` +#### 11.加载图片回调信息数据 示例 +``` +ImageKnifeComponent({ ImageKnifeOption: = new ImageKnifeOption({ + loadSrc: $r('app.media.pngSample'), + objectFit: ImageFit.Contain, + onLoadListener: { + onLoadStart: (req) => { + let startCallBackData = JSON.stringify(req?.getImageKnifeData()); + }, + onLoadFailed: (res, req) => { + let failedBackData = res + ";" + JSON.stringify(req?.getImageKnifeData()); + }, + onLoadSuccess: (data, imageData, req) => { + let successBackData = JSON.stringify(req?.getImageKnifeData()); + }, + onLoadCancel: (res, req) => { + let cancelBackData = res + ";" + JSON.stringify(req?.getImageKnifeData()); + } + }, + border: { radius: 50 }, + onComplete: (event) => { + if (event && event.loadingStatus == 0) { + let render_success = JSON.stringify(Date.now()) + } + } + }) +}).width(100).height(100) +``` + #### Reuse Scenario Clear the component content in the **aboutToRecycle** lifecycle and trigger image loading through watch observeration. ## Available APIs @@ -326,18 +355,18 @@ Clear the component content in the **aboutToRecycle** lifecycle and trigger imag | putCacheImage | url: string, pixelMap: PixelMap, cacheType: CacheStrategy = CacheStrategy.Default, signature?: string | Writes to the memory disk cache. | | removeMemoryCache | url: string | Removes an entry from the memory cache. | | removeFileCache | url: string | Removes an entry from the file cache. | -| getCacheLimitSize | cacheType?: CacheStrategy | Gets the upper limit size of the specified cache | -| getCurrentCacheNum | cacheType?: CacheStrategy | Gets the number of images currently cached in the specified cache | -| getCurrentCacheSize | cacheType?: CacheStrategy | Gets the current size of the specified cache | +| getCurrentCacheNum | cacheType?: CacheStrategy | 获取指定缓存的当前缓存图片个数 | +| getCurrentCacheSize | cacheType?: CacheStrategy | 获取指定缓存的当前大小 | +| getCurrentCacheSize | cacheType?: CacheStrategy | 获取指定缓存的当前大小 | -### Description of the callback interface -| Callback interface | Callback field | Description of callback data | -|------------------------------|-------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| onLoadStart | req: ImageKnifeRequest | The req return field contains information about the image request, such as the url of the image and the width and height of its components, while ImageKnifeRequest contains ImageKnifeData, which contains the start of the request and the point at which it checks the memory cache | -| onLoadSuccess | data: string \| PixelMap \| undefined, imageData: ImageKnifeData, req?: ImageKnifeRequest | data: The result data is loaded successfully. imageData: information stored in the cache of the image, req: information of the image request, and ImageKnifeData, including the original size of the image in the request, image decoding size, format, picture frame, request end time, disk check time, network request start and end, image decoding start and end and other time points | -| onLoadFailed | err: string, req?: ImageKnifeRequest | err: indicates error information. req: Information of image request. Meanwhile, ImageKnifeData contains the error information of the request (ErrorInfo, TimeInfo). ErrorInfo contains the error phase, error code and the error code of network request. TimeInfo contains the end time of the request, the disk check time, the start and end of the network request, and the start and end of the image decoding | -| onLoadCancel | reason: string, req?: ImageKnifeRequest | reason: indicates the reason for canceling the callback. req: Information of image request. Meanwhile, ImageKnifeData contains the error information of the request (ErrorInfo, TimeInfo). ErrorInfo contains the error phase, error code and the error code of network request. TimeInfo contains the end time of the request, the disk check time, the start and end of the network request, the start and end of the image decoding, and the request cancellation | +### 回调接口说明 +| 回调接口 | 回调字段 | 回调描述 | +|----------------|-------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| onLoadStart | req: ImageKnifeRequest | req返回字段中包含了图片请求的信息,如图片的url及其组件的宽高,同时ImageKnifeRequest包含了ImageKnifeData,其中包含此次请求的开始及其检查内存缓存的时间点 | +| onLoadSuccess | data: string \| PixelMap \| undefined, imageData: ImageKnifeData, req?: ImageKnifeRequest | data:加载成功的结果数据;imageData:图片的存入缓存中的信息 ,req:图片请求的信息,同时其中的ImageKnifeData,包含此次请求中图片的原始大小、图片的解码大小、格式、图片帧、请求结束时间、磁盘检查时间、网络请求开始结束、图片解码开始结束等时间点 | +| onLoadFailed | err: string, req?: ImageKnifeRequest | err:错误信息描述;req:图片请求的信息,同时其中的ImageKnifeData,包含此次请求错误信息(ErrorInfo,TimeInfo),ErrorInfo其中包含了,错误阶段、错误码及其网络请求的错误码;TimeInfo中包含请求结束时间、磁盘检查时间、网络请求开始结束、图片解码开始结束等时间点 | +| onLoadCancel | reason: string, req?: ImageKnifeRequest | reason:取消回调原因;req:图片请求的信息,同时其中的ImageKnifeData,包含此次请求错误信息(ErrorInfo,TimeInfo),ErrorInfo其中包含了,错误阶段、错误码及其网络请求的错误码;TimeInfo中包含请求结束时间、磁盘检查时间、网络请求开始结束、图片解码开始结束及其请求取消等时间点 | ### Graphics tRansformation Types (GPUImage Dependency Required) diff --git a/README_zh.md b/README_zh.md index 86cd27a..540aeb8 100644 --- a/README_zh.md +++ b/README_zh.md @@ -265,6 +265,36 @@ ImageKnifeAnimatorComponent({ }).width(300).height(300).backgroundColor(Color.Orange).margin({top:30}) ``` +#### 11.加载图片回调信息数据 示例 +``` +ImageKnifeComponent({ ImageKnifeOption: = new ImageKnifeOption({ + loadSrc: $r('app.media.pngSample'), + objectFit: ImageFit.Contain, + onLoadListener: { + onLoadStart: (req) => { + let startCallBackData = JSON.stringify(req?.getImageKnifeData()); + }, + onLoadFailed: (res, req) => { + let failedBackData = res + ";" + JSON.stringify(req?.getImageKnifeData()); + }, + onLoadSuccess: (data, imageData, req) => { + let successBackData = JSON.stringify(req?.getImageKnifeData()); + }, + onLoadCancel: (res, req) => { + let cancelBackData = res + ";" + JSON.stringify(req?.getImageKnifeData()); + } + }, + border: { radius: 50 }, + onComplete: (event) => { + if (event && event.loadingStatus == 0) { + let render_success = JSON.stringify(Date.now()) + } + } + }) +}).width(100).height(100) +``` + + #### 复用场景 在aboutToRecycle生命周期清空组件内容;通过watch监听触发图片的加载。 ## 接口说明 @@ -334,7 +364,7 @@ ImageKnifeAnimatorComponent({ | getCurrentCacheSize | cacheType?: CacheStrategy | 获取指定缓存的当前大小 | | getCurrentCacheSize | cacheType?: CacheStrategy | 获取指定缓存的当前大小 | -### 回调接口字段说明 +### 回调接口说明 | 回调接口 | 回调字段 | 回调描述 | |----------------|-------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------| | onLoadStart | req: ImageKnifeRequest | req返回字段中包含了图片请求的信息,如图片的url及其组件的宽高,同时ImageKnifeRequest包含了ImageKnifeData,其中包含此次请求的开始及其检查内存缓存的时间点 | From 1153390393a7ebb7937c39cd1db08ce87e1dfbf4 Mon Sep 17 00:00:00 2001 From: tyBrave Date: Tue, 29 Oct 2024 16:42:32 +0800 Subject: [PATCH 6/6] =?UTF-8?q?=E4=BF=9D=E6=8C=81=E8=8B=B1=E6=96=87README?= =?UTF-8?q?=E4=B8=8D=E5=8F=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: tyBrave --- README.md | 47 ++--------------------------------------------- 1 file changed, 2 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 5641fc7..7d33085 100644 --- a/README.md +++ b/README.md @@ -264,36 +264,6 @@ ImageKnifeAnimatorComponent({ },animatorOption:this.animatorOption }).width(300).height(300).backgroundColor(Color.Orange).margin({top:30}) ``` - -#### 11.加载图片回调信息数据 示例 -``` -ImageKnifeComponent({ ImageKnifeOption: = new ImageKnifeOption({ - loadSrc: $r('app.media.pngSample'), - objectFit: ImageFit.Contain, - onLoadListener: { - onLoadStart: (req) => { - let startCallBackData = JSON.stringify(req?.getImageKnifeData()); - }, - onLoadFailed: (res, req) => { - let failedBackData = res + ";" + JSON.stringify(req?.getImageKnifeData()); - }, - onLoadSuccess: (data, imageData, req) => { - let successBackData = JSON.stringify(req?.getImageKnifeData()); - }, - onLoadCancel: (res, req) => { - let cancelBackData = res + ";" + JSON.stringify(req?.getImageKnifeData()); - } - }, - border: { radius: 50 }, - onComplete: (event) => { - if (event && event.loadingStatus == 0) { - let render_success = JSON.stringify(Date.now()) - } - } - }) -}).width(100).height(100) -``` - #### Reuse Scenario Clear the component content in the **aboutToRecycle** lifecycle and trigger image loading through watch observeration. ## Available APIs @@ -337,7 +307,7 @@ Clear the component content in the **aboutToRecycle** lifecycle and trigger imag | transformation | PixelMapTransformation | Image transformation. Optional. | | drawingColorFilter | ColorFilter | Drawing color filter. Optional. | | onComplete | (event:EventImage \| undefined)=>void | Callback for image loading completion. Optional. | -| onLoadListener | onLoadStart?: (req?: ImageKnifeRequest) => void,onLoadSuccess?: (data: string \| PixelMap \| undefined, imageData: ImageKnifeData, req?: ImageKnifeRequest) => void,onLoadFailed?: (err: string, req?: ImageKnifeRequest) => void,onLoadCancel?: (res: string, req?: ImageKnifeRequest) => void | Callback for image loading events. Optional. | +| onLoadListener | onLoadStart:()=>void,onLoadSuccess:(data:string\|Pixelmap)=>void | Callback for image loading events. Optional. | ### ImageKnife @@ -355,19 +325,6 @@ Clear the component content in the **aboutToRecycle** lifecycle and trigger imag | putCacheImage | url: string, pixelMap: PixelMap, cacheType: CacheStrategy = CacheStrategy.Default, signature?: string | Writes to the memory disk cache. | | removeMemoryCache | url: string | Removes an entry from the memory cache. | | removeFileCache | url: string | Removes an entry from the file cache. | -| getCurrentCacheNum | cacheType?: CacheStrategy | 获取指定缓存的当前缓存图片个数 | -| getCurrentCacheSize | cacheType?: CacheStrategy | 获取指定缓存的当前大小 | -| getCurrentCacheSize | cacheType?: CacheStrategy | 获取指定缓存的当前大小 | - - -### 回调接口说明 -| 回调接口 | 回调字段 | 回调描述 | -|----------------|-------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| onLoadStart | req: ImageKnifeRequest | req返回字段中包含了图片请求的信息,如图片的url及其组件的宽高,同时ImageKnifeRequest包含了ImageKnifeData,其中包含此次请求的开始及其检查内存缓存的时间点 | -| onLoadSuccess | data: string \| PixelMap \| undefined, imageData: ImageKnifeData, req?: ImageKnifeRequest | data:加载成功的结果数据;imageData:图片的存入缓存中的信息 ,req:图片请求的信息,同时其中的ImageKnifeData,包含此次请求中图片的原始大小、图片的解码大小、格式、图片帧、请求结束时间、磁盘检查时间、网络请求开始结束、图片解码开始结束等时间点 | -| onLoadFailed | err: string, req?: ImageKnifeRequest | err:错误信息描述;req:图片请求的信息,同时其中的ImageKnifeData,包含此次请求错误信息(ErrorInfo,TimeInfo),ErrorInfo其中包含了,错误阶段、错误码及其网络请求的错误码;TimeInfo中包含请求结束时间、磁盘检查时间、网络请求开始结束、图片解码开始结束等时间点 | -| onLoadCancel | reason: string, req?: ImageKnifeRequest | reason:取消回调原因;req:图片请求的信息,同时其中的ImageKnifeData,包含此次请求错误信息(ErrorInfo,TimeInfo),ErrorInfo其中包含了,错误阶段、错误码及其网络请求的错误码;TimeInfo中包含请求结束时间、磁盘检查时间、网络请求开始结束、图片解码开始结束及其请求取消等时间点 | - ### Graphics tRansformation Types (GPUImage Dependency Required) | Type | Description | @@ -415,4 +372,4 @@ This project is licensed under [Apache License 2.0](https://gitee.com/openharmon ## Known Issues - The **ImageFit** attribute cannot be set for the **ImageKnifeAnimator** component. -- The **border** attribute of the **ImageKnifeAnimator** component cannot make the image rounded corners. +- The **border** attribute of the **ImageKnifeAnimator** component cannot make the image rounded corners. \ No newline at end of file