From 2320d303cdf7deb88fb2e57e0f575aa9ad13f0d5 Mon Sep 17 00:00:00 2001 From: zhang_hanyong Date: Mon, 22 Apr 2024 17:12:11 +0800 Subject: [PATCH] =?UTF-8?q?ImageKnife=E5=8A=A0=E8=BD=BD=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=87=AA=E5=AE=9A=E4=B9=89=E7=BD=91=E7=BB=9C?= =?UTF-8?q?=E6=A0=88=E5=92=8C=E5=9B=BE=E7=89=87=E5=8A=A0=E8=BD=BD=E7=BB=84?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: zhang_hanyong --- .../ets/pages/imageknifeTestCaseIndex.ets | 8 + .../testCustomDataFetchClientWithPage.ets | 239 ++++++++++++++++++ .../resources/base/profile/main_pages.json | 3 +- .../ets/components/imageknife/ImageKnife.ets | 8 +- .../imageknife/ImageKnifeComponent.ets | 1 + .../imageknife/ImageKnifeOption.ets | 4 + .../components/imageknife/RequestOption.ets | 3 + .../ets/components/imageknife/TaskParams.ets | 2 + .../networkmanage/CustomDataFetchClient.ets | 111 ++++++++ .../networkmanage/DownloadClient.ets | 17 +- .../networkmanage/HttpDownloadClient.ets | 1 + 11 files changed, 388 insertions(+), 9 deletions(-) create mode 100644 entry/src/main/ets/pages/testCustomDataFetchClientWithPage.ets create mode 100644 library/src/main/ets/components/imageknife/networkmanage/CustomDataFetchClient.ets diff --git a/entry/src/main/ets/pages/imageknifeTestCaseIndex.ets b/entry/src/main/ets/pages/imageknifeTestCaseIndex.ets index ff793cb..bf154c2 100644 --- a/entry/src/main/ets/pages/imageknifeTestCaseIndex.ets +++ b/entry/src/main/ets/pages/imageknifeTestCaseIndex.ets @@ -396,6 +396,14 @@ struct IndexFunctionDemo { router.pushUrl({ url: 'pages/testImageKnifeHeic' }); }).margin({ top: 5, left: 3 }) }.width('100%').height(60).backgroundColor(Color.Pink) + + Text('测试加载图片自定义网络栈').fontSize(15) + Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { + Button('加载图片自定义网络栈') + .onClick(() => { + router.pushUrl({ url: 'pages/testCustomDataFetchClientWithPage' }); + }).margin({ top: 5, left: 3 }) + }.width('100%').height(60).backgroundColor(Color.Pink) } } .width('100%') diff --git a/entry/src/main/ets/pages/testCustomDataFetchClientWithPage.ets b/entry/src/main/ets/pages/testCustomDataFetchClientWithPage.ets new file mode 100644 index 0000000..1de5fc8 --- /dev/null +++ b/entry/src/main/ets/pages/testCustomDataFetchClientWithPage.ets @@ -0,0 +1,239 @@ +/* + * 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 { + DataFetchResult, + ImageKnifeComponent, + ImageKnifeGlobal, + ImageKnifeOption, + ScaleType +} from '@ohos/libraryimageknife'; +import http from '@ohos.net.http'; +import { DownloadClient } from '@ohos/imageknife/src/main/ets/components/imageknife/networkmanage/DownloadClient'; +import { + CustomDataFetchClient +} from '@ohos/imageknife/src/main/ets/components/imageknife/networkmanage/CustomDataFetchClient'; + +class CommonDataSource implements IDataSource { + private dataArray: T[] = [] + private listeners: DataChangeListener[] = [] + + constructor(element: []) { + this.dataArray = element + } + + public getData(index: number) { + return this.dataArray[index] + } + + public totalCount(): number { + return this.dataArray.length + } + + public addData(index: number, data: T[]): void { + this.dataArray = this.dataArray.concat(data) + this.notifyDataAdd(index) + } + + unregisterDataChangeListener(listener: DataChangeListener): void { + const pos = this.listeners.indexOf(listener); + if (pos >= 0) { + this.listeners.splice(pos, 1); + } + } + + registerDataChangeListener(listener: DataChangeListener): void { + if (this.listeners.indexOf(listener) < 0) { + this.listeners.push(listener) + } + } + + notifyDataAdd(index: number): void { + this.listeners.forEach((listener: DataChangeListener) => { + listener.onDataAdd(index) + }) + } +} + +@Entry +@Component +struct TestCustomDataFetchClientWithPage { + @State hotCommendList: CommonDataSource = new CommonDataSource([]) + @State singleImageKnifeOption: ImageKnifeOption = + { + loadSrc: $r('app.media.icon'), + placeholderSrc: $r('app.media.icon_loading'), + errorholderSrc: $r('app.media.icon_failed'), + }; + @State isSingleImageVisible: boolean = true; + @State isAllImageVisible: boolean = false; + @State isCustom: boolean = false; + private data: Array = [ + "http://img2.xkhouse.com/bbs/hfhouse/data/attachment/forum/corebbs/2009-11/2009113011534566298.jpg", + "http://a.hiphotos.baidu.com/image/pic/item/e824b899a9014c087eb617650e7b02087af4f464.jpg" + ] + private addData: Array = [ + "http://c.hiphotos.baidu.com/image/pic/item/9c16fdfaaf51f3de1e296fa390eef01f3b29795a.jpg", + "http://h.hiphotos.baidu.com/image/pic/item/902397dda144ad340668b847d4a20cf430ad851e.jpg" + + ] + private cancelData: Array = [ + "http://b.hiphotos.baidu.com/image/pic/item/359b033b5bb5c9ea5c0e3c23d139b6003bf3b374.jpg", + "http://a.hiphotos.baidu.com/image/pic/item/8d5494eef01f3a292d2472199d25bc315d607c7c.jpg" + ] + + aboutToAppear(): void { + this.hotCommendList.addData(this.hotCommendList.totalCount(), this.data) + console.info('TestCustomDataFetch about to appear.') + } + + build() { + Scroll() { + Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { + Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { + + Button("单个图片").margin(16).onClick(() => { + console.log('TestCustomDataFetch click single.'); + this.isSingleImageVisible = true; + this.isAllImageVisible = false; + ImageKnifeGlobal.getInstance().getImageKnife()?.replaceDataFetch(new CustomDataFetchClient()); + + this.singleImageKnifeOption = { + loadSrc: 'https://i03piccdn.sogoucdn.com/b49514f605c78e39', + placeholderSrc: $r('app.media.icon_loading'), + errorholderSrc: $r('app.media.icon_failed'), + customGetImage: custom + } + }) + Button("全部图片").margin(16).onClick(() => { + console.log('TestCustomDataFetch click all.'); + this.isSingleImageVisible = false; + this.isAllImageVisible = true; + + ImageKnifeGlobal.getInstance().getImageKnife()?.replaceDataFetch(new CustomDataFetchClient()); + this.hotCommendList.addData(this.hotCommendList.totalCount(), this.addData) + + }) + + Button("取消自定义全部图片").margin(16).onClick(() => { + console.log('TestCustomDataFetch click cancel.'); + this.isSingleImageVisible = false; + this.isAllImageVisible = true; + + ImageKnifeGlobal.getInstance().getImageKnife()?.replaceDataFetch(new DownloadClient()); + this.hotCommendList.addData(this.hotCommendList.totalCount(), this.cancelData) + }) + } + + // 单个图片使用自定义网络栈 + ImageKnifeComponent({ imageKnifeOption: this.singleImageKnifeOption }) + .width(200) + .height(200) + .margin({ top: 50 }) + .visibility(this.isSingleImageVisible ? Visibility.Visible : Visibility.None) + + // 全部图片使用自定义网络栈 + Column() { + Grid() { + LazyForEach(this.hotCommendList, (item: string) => { + GridItem() { + ImageKnifeComponent({ + imageKnifeOption: { + loadSrc: item, + placeholderSrc: $r('app.media.icon'), + errorholderSrc: $r('app.media.icon_failed'), + placeholderScaleType: ScaleType.CENTER_CROP, + mainScaleType: ScaleType.CENTER_CROP, + } + }).width('100%').height('100%') + }.width('40%').height(200) + }, (item: string) => JSON.stringify(item)) + } + .columnsTemplate('1fr 1fr') + .columnsGap(8) + .rowsGap(10) + .width('100%') + .hitTestBehavior(HitTestMode.None) + .maxCount(10) + }.margin({ top: 5 }) + .visibility(this.isAllImageVisible ? Visibility.Visible : Visibility.None) + } + } + .width('100%') + .height('100%') + } +} + +@Concurrent +async function custom(context: Context, loadSrc: string): Promise { + let result: DataFetchResult = new DataFetchResult(); + try { + let arrayBuffers = new Array(); + let httpRequest = http.createHttp() + + httpRequest.on('headersReceive', (header: Object) => { + // 跟服务器连接成功准备下载 + }) + httpRequest.on('dataReceive', (data: ArrayBuffer) => { + // 下载数据流多次返回 + arrayBuffers.push(data); + }) + httpRequest.on('dataEnd', () => { + // 下载完毕 + + }) + + const resultCode = await httpRequest.requestInStream(loadSrc as string, + { + method: http.RequestMethod.GET, + expectDataType: http.HttpDataType.ARRAY_BUFFER, + connectTimeout: 60000, // 可选 默认60000ms + readTimeout: 0, // 可选, 默认为60000ms + usingProtocol: http.HttpProtocol.HTTP1_1, // 可选,协议类型默认值由系统自动指定 + usingCache: false + }).catch((err: Error) => { + result.error = 'TestCustomDataFetchClientWithPage requestInStream error.' + JSON.stringify(err); + }) + if (resultCode == 200) { + //let combineArray = this.combineArrayBuffers(arrayBuffers); + // 计算多个ArrayBuffer的总字节大小 + let totalByteLength = 0; + for (const arrayBuffer of arrayBuffers) { + totalByteLength += arrayBuffer.byteLength; + } + // 创建一个新的ArrayBuffer + const combinedArrayBuffer = new ArrayBuffer(totalByteLength); + + // 创建一个Uint8Array来操作新的ArrayBuffer + const combinedUint8Array = new Uint8Array(combinedArrayBuffer); + + // 依次复制每个ArrayBuffer的内容到新的ArrayBuffer中 + let offset = 0; + for (const arrayBuffer of arrayBuffers) { + const sourceUint8Array = new Uint8Array(arrayBuffer); + combinedUint8Array.set(sourceUint8Array, offset); + offset += sourceUint8Array.length; + } + result.data = combinedArrayBuffer; + } else { + result.error = 'TestCustomDataFetchClientWithPage error. resultCode = ' + resultCode; + } + console.log('TestCustomDataFetch signal onComplete, code = ' + resultCode + ',length = ' + result.data?.byteLength); + } catch (error) { + result.error = 'TestCustomDataFetchClientWithPage error' + error.stack; + } + return result; +} + + diff --git a/entry/src/main/resources/base/profile/main_pages.json b/entry/src/main/resources/base/profile/main_pages.json index 9992807..a5de303 100644 --- a/entry/src/main/resources/base/profile/main_pages.json +++ b/entry/src/main/resources/base/profile/main_pages.json @@ -53,6 +53,7 @@ "pages/webpImageTestPage", "pages/testStopPlayingGifPage", "pages/testImageKnifeDataFetch", - "pages/testImageKnifeHeic" + "pages/testImageKnifeHeic", + "pages/testCustomDataFetchClientWithPage" ] } \ No newline at end of file diff --git a/library/src/main/ets/components/imageknife/ImageKnife.ets b/library/src/main/ets/components/imageknife/ImageKnife.ets index 4e0ba52..7e36aa3 100644 --- a/library/src/main/ets/components/imageknife/ImageKnife.ets +++ b/library/src/main/ets/components/imageknife/ImageKnife.ets @@ -672,7 +672,8 @@ export class ImageKnife { loadSrc: this.transformResource(request.loadSrc) as string | PixelMap | MResource, placeholderSrc: this.transformResource(request.placeholderSrc) as PixelMap | MResource | undefined, errorholderSrc: this.transformResource(request.errorholderSrc) as PixelMap | MResource | undefined, - retryholderSrc:this.transformResource(request.retryholderSrc) as PixelMap | MResource | undefined + retryholderSrc:this.transformResource(request.retryholderSrc) as PixelMap | MResource | undefined, + customGetImage: request.customGetImage } return data; } @@ -906,8 +907,8 @@ async function taskExecute(sendData:SendableData,taskData:TaskParams): Promise

{ diff --git a/library/src/main/ets/components/imageknife/ImageKnifeComponent.ets b/library/src/main/ets/components/imageknife/ImageKnifeComponent.ets index 3fe841a..490123c 100644 --- a/library/src/main/ets/components/imageknife/ImageKnifeComponent.ets +++ b/library/src/main/ets/components/imageknife/ImageKnifeComponent.ets @@ -338,6 +338,7 @@ export struct ImageKnifeComponent { this.resetGifData() let request = new RequestOption(); this.detachFromLayout = request.detachFromLayout; + request.customGetImage = this.imageKnifeOption.customGetImage; this.configNecessary(request); this.configCacheStrategy(request); this.configDisplay(request); diff --git a/library/src/main/ets/components/imageknife/ImageKnifeOption.ets b/library/src/main/ets/components/imageknife/ImageKnifeOption.ets index 8df8ae9..407a693 100644 --- a/library/src/main/ets/components/imageknife/ImageKnifeOption.ets +++ b/library/src/main/ets/components/imageknife/ImageKnifeOption.ets @@ -27,6 +27,7 @@ import { RoundCorner } from './transform/RoundedCornersTransformation' import { ObjectKey } from './ObjectKey' import common from '@ohos.app.ability.common' import { Priority } from './RequestOption' +import { DataFetchResult } from './networkmanage/DataFetchResult' export interface CropCircleWithBorder{ border: number, @@ -143,6 +144,9 @@ export class ImageKnifeOption { context?: common.UIAbilityContext; // sizeAnimate?: AnimateParam 由业务自定义不再支持 + // 设置是否使用应用自定义的方式加载图片 + customGetImage?: (context: Context, src: string) => Promise; + constructor() { } diff --git a/library/src/main/ets/components/imageknife/RequestOption.ets b/library/src/main/ets/components/imageknife/RequestOption.ets index 63deff9..64c3f06 100644 --- a/library/src/main/ets/components/imageknife/RequestOption.ets +++ b/library/src/main/ets/components/imageknife/RequestOption.ets @@ -59,6 +59,7 @@ import { DiskLruCache } from '../cache/DiskLruCache' import { SparkMD5 } from '../3rd_party/sparkmd5/spark-md5' import { FileUtils } from '../cache/FileUtils' import util from '@ohos.util' +import { DataFetchResult } from './networkmanage/DataFetchResult' export interface Size { width: number, @@ -141,6 +142,8 @@ export class RequestOption { errorholderCacheKey: string = ""; // 自定义缓存关键字 signature?: ObjectKey; + // 设置是否使用应用自定义的方式加载图片 + customGetImage?: (context: Context, src: string) => Promise; // 下载原始文件地址 downloadFilePath: string = ""; //磁盘缓存文件路径 diff --git a/library/src/main/ets/components/imageknife/TaskParams.ets b/library/src/main/ets/components/imageknife/TaskParams.ets index 9a37c9a..cc9b11b 100644 --- a/library/src/main/ets/components/imageknife/TaskParams.ets +++ b/library/src/main/ets/components/imageknife/TaskParams.ets @@ -16,6 +16,7 @@ import { ObjectKey } from './ObjectKey'; import { Priority, Size } from '../imageknife/RequestOption' import common from '@ohos.app.ability.common' import { MResource } from './utils/MResource'; +import { DataFetchResult } from './networkmanage/DataFetchResult'; export class TaskParams { headers: Map = new Map(); @@ -26,4 +27,5 @@ export class TaskParams { placeholderSrc: PixelMap | MResource | undefined = undefined; errorholderSrc: PixelMap | MResource | undefined = undefined; retryholderSrc: PixelMap | MResource | undefined = undefined; + customGetImage?: (context: Context, loadSrc: string) => Promise; } \ No newline at end of file diff --git a/library/src/main/ets/components/imageknife/networkmanage/CustomDataFetchClient.ets b/library/src/main/ets/components/imageknife/networkmanage/CustomDataFetchClient.ets new file mode 100644 index 0000000..4a05190 --- /dev/null +++ b/library/src/main/ets/components/imageknife/networkmanage/CustomDataFetchClient.ets @@ -0,0 +1,111 @@ +/* + * 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 { IDataFetch } from '../networkmanage/IDataFetch' +import { RequestOption } from '../RequestOption' +import { RequestData } from './RequestData' +import { ImageKnifeGlobal } from '../ImageKnifeGlobal' +import common from '@ohos.app.ability.common' +import http from '@ohos.net.http' +import { DataFetchResult } from './DataFetchResult' + +@Sendable +export class CustomDataFetchClient implements IDataFetch { + async loadData(request: RequestOption) { + let result: DataFetchResult = new DataFetchResult(); + if (!request || typeof request.loadSrc !== 'string') { + result.error = 'CustomDataFetchClient request or loadSrc error.'; + return result; + } + if (request.customGetImage) { + return await request.customGetImage(ImageKnifeGlobal.getInstance() + .getHapContext() as common.UIAbilityContext, request.loadSrc); + } + try { + let httpRequest = http.createHttp() + let arrayBuffers = new Array(); + httpRequest.on('headersReceive', (header: Object) => { + // 跟服务器连接成功准备下载 + if (request.progressFunc) { + // 进度条为0 + request.progressFunc.asyncSuccess(0) + } + }) + httpRequest.on('dataReceive', (data: ArrayBuffer) => { + // 下载数据流多次返回 + arrayBuffers.push(data); + }) + httpRequest.on('dataReceiveProgress', (data: RequestData) => { + // 下载进度 + if (data != undefined && (typeof data.receiveSize == 'number') && (typeof data.totalSize == 'number')) { + let percent = Math.round(((data.receiveSize * 1.0) / (data.totalSize * 1.0)) * 100) + if (request.progressFunc) { + request.progressFunc.asyncSuccess(percent) + } + } + }) + httpRequest.on('dataEnd', () => { + // 下载完毕 + }) + const headerObj: Record = {} + request.headers.forEach((value, key) => { + headerObj[key] = value + }) + const data = await httpRequest.requestInStream(request.loadSrc as string, { + header: headerObj, + method: http.RequestMethod.GET, + expectDataType: http.HttpDataType.ARRAY_BUFFER, + connectTimeout: 60000, // 可选 默认60000ms + readTimeout: 0, // 可选, 默认为60000ms + usingProtocol: http.HttpProtocol.HTTP1_1, // 可选,协议类型默认值由系统自动指定 + usingCache: false + }).catch((err: Error) => { + result.error = 'CustomDataFetchClient has error, http code = ' + JSON.stringify(err); + }) + if (data == 200) { + result.data = this.combineArrayBuffers(arrayBuffers); + } else { + result.error = 'CustomDataFetchClient resultCode error, code = ' + data; + } + console.log('TestCustomDataFetch all onComplete, code = ' + data + ',length = ' + result.data?.byteLength); + } catch (err) { + result.error = 'CustomDataFetchClient catch err request uuid =' + request.uuid; + } + return result; + } + + combineArrayBuffers(arrayBuffers: ArrayBuffer[]): ArrayBuffer { + // 计算多个ArrayBuffer的总字节大小 + let totalByteLength = 0; + for (const arrayBuffer of arrayBuffers) { + totalByteLength += arrayBuffer.byteLength; + } + + // 创建一个新的ArrayBuffer + const combinedArrayBuffer = new ArrayBuffer(totalByteLength); + + // 创建一个Uint8Array来操作新的ArrayBuffer + const combinedUint8Array = new Uint8Array(combinedArrayBuffer); + + // 依次复制每个ArrayBuffer的内容到新的ArrayBuffer中 + let offset = 0; + for (const arrayBuffer of arrayBuffers) { + const sourceUint8Array = new Uint8Array(arrayBuffer); + combinedUint8Array.set(sourceUint8Array, offset); + offset += sourceUint8Array.length; + } + return combinedArrayBuffer; + } +} \ No newline at end of file diff --git a/library/src/main/ets/components/imageknife/networkmanage/DownloadClient.ets b/library/src/main/ets/components/imageknife/networkmanage/DownloadClient.ets index ed1c4e6..c769b17 100644 --- a/library/src/main/ets/components/imageknife/networkmanage/DownloadClient.ets +++ b/library/src/main/ets/components/imageknife/networkmanage/DownloadClient.ets @@ -35,11 +35,7 @@ export class DownloadClient implements IDataFetch { async loadData(request: RequestOption): Promise { if (typeof request.loadSrc == 'string') { - let fileDir:string = (ImageKnifeGlobal.getInstance().getHapContext() as common.UIAbilityContext).filesDir as string; - let cacheDir:string = (ImageKnifeGlobal.getInstance().getHapContext() as common.UIAbilityContext).cacheDir as string - - if (request.loadSrc.startsWith(fileDir) || - request.loadSrc.startsWith(cacheDir)) { + if (this.isLocalLoadSrc(ImageKnifeGlobal.getInstance().getHapContext(), request.loadSrc)) { // 本地沙盒 return this.localFileClient.loadData(request) } else if (request.loadSrc.startsWith('datashare://') || request.loadSrc.startsWith('file://')) { @@ -54,4 +50,15 @@ export class DownloadClient implements IDataFetch { return result; } } + + isLocalLoadSrc(context: Object | undefined, loadSrc: string): boolean { + if (context != undefined) { + let fileDir: string = (context as common.UIAbilityContext).filesDir as string; + let cacheDir: string = (context as common.UIAbilityContext).cacheDir as string + if (loadSrc.startsWith(fileDir) || loadSrc.startsWith(cacheDir)) { + return true; + } + } + return false; + } } \ No newline at end of file diff --git a/library/src/main/ets/components/imageknife/networkmanage/HttpDownloadClient.ets b/library/src/main/ets/components/imageknife/networkmanage/HttpDownloadClient.ets index 1806806..c54f6e5 100644 --- a/library/src/main/ets/components/imageknife/networkmanage/HttpDownloadClient.ets +++ b/library/src/main/ets/components/imageknife/networkmanage/HttpDownloadClient.ets @@ -70,6 +70,7 @@ export class HttpDownloadClient implements IDataFetch { } else { result.error = `HttpDownloadClient has error, http code = ` + JSON.stringify(data); } + console.log('TestCustomDataFetch http onComplete, code = ' + data + ',length = ' + result.data?.byteLength); } catch (err) { result.error ='HttpDownloadClient catch err request uuid =' + request.uuid; }