ref |> 文章详情页增加Web容器复用逻辑01
Signed-off-by: xugenyuan <xugenyuan@wondertek.com.cn>
Showing
6 changed files
with
436 additions
and
75 deletions
| 1 | export { WdWebComponent } from './src/main/ets/pages/WdWebComponent' | 1 | export { WdWebComponent } from './src/main/ets/pages/WdWebComponent' |
| 2 | 2 | ||
| 3 | -export { WdWebLocalComponent } from './src/main/ets/pages/WdWebLocalComponent' | ||
| 3 | +export { WdWebLocalComponent } from './src/main/ets/pages/WdWebLocalComponent' | ||
| 4 | + | ||
| 5 | +export { WebEvents, WebNodeController } from './src/main/ets/webPool/WebLocalPool' | ||
| 6 | +export { WebPoolManager } from './src/main/ets/webPool/WebPoolManager' | ||
| 7 | +export { WebArticleEventHandler } from './src/main/ets/webPool/WebArticleEventHandler' |
| @@ -9,18 +9,21 @@ import { window } from '@kit.ArkUI'; | @@ -9,18 +9,21 @@ import { window } from '@kit.ArkUI'; | ||
| 9 | import { NativeCallH5Type,NativeCallH5Event,eventParams } from './NativeCallH5Type'; | 9 | import { NativeCallH5Type,NativeCallH5Event,eventParams } from './NativeCallH5Type'; |
| 10 | import { AudioSuspensionModel } from '../viewmodel/AudioSuspensionModel' | 10 | import { AudioSuspensionModel } from '../viewmodel/AudioSuspensionModel' |
| 11 | import { BusinessError } from '@kit.BasicServicesKit'; | 11 | import { BusinessError } from '@kit.BasicServicesKit'; |
| 12 | +import { WebPoolManager } from '../webPool/WebPoolManager'; | ||
| 13 | +import { WebEvents } from '../webPool/WebLocalPool'; | ||
| 12 | 14 | ||
| 13 | const TAG = 'WdWebLocalComponent'; | 15 | const TAG = 'WdWebLocalComponent'; |
| 14 | 16 | ||
| 15 | @Component | 17 | @Component |
| 16 | export struct WdWebLocalComponent { | 18 | export struct WdWebLocalComponent { |
| 19 | + webPoolTargetId: string = '' | ||
| 17 | webviewControl: BridgeWebViewControl = new BridgeWebViewControl() | 20 | webviewControl: BridgeWebViewControl = new BridgeWebViewControl() |
| 18 | onWebPrepared: () => void = () => { | 21 | onWebPrepared: () => void = () => { |
| 19 | } | 22 | } |
| 20 | @Prop backVisibility: boolean = false | 23 | @Prop backVisibility: boolean = false |
| 21 | @Prop webResource: Resource = {} as Resource | 24 | @Prop webResource: Resource = {} as Resource |
| 22 | @Prop @Watch('onReloadStateChanged') reload: number = 0 | 25 | @Prop @Watch('onReloadStateChanged') reload: number = 0 |
| 23 | - @State webHeight: string | number = '100%' | 26 | + @State webHeight: Length = '100%' |
| 24 | @Link isPageEnd: boolean | 27 | @Link isPageEnd: boolean |
| 25 | @State videoUrl: string = '' | 28 | @State videoUrl: string = '' |
| 26 | @State positionWidth: number = 0 | 29 | @State positionWidth: number = 0 |
| @@ -75,6 +78,18 @@ export struct WdWebLocalComponent { | @@ -75,6 +78,18 @@ export struct WdWebLocalComponent { | ||
| 75 | } | 78 | } |
| 76 | 79 | ||
| 77 | aboutToAppear(): void { | 80 | aboutToAppear(): void { |
| 81 | + | ||
| 82 | + let eventHandler = WebPoolManager.sharedInstance().getWebEventHandler(this.webPoolTargetId) | ||
| 83 | + if (!eventHandler) { | ||
| 84 | + Logger.error(TAG, '当前target不应拿不到nodeController'); | ||
| 85 | + } else { | ||
| 86 | + eventHandler.pageEndBlock = this.onPageEnd.bind(this) | ||
| 87 | + if (eventHandler.pageLoadEnd) { | ||
| 88 | + this.onPageEnd() | ||
| 89 | + } | ||
| 90 | + eventHandler.currentPageOperateBlock = this.currentPageOperate.bind(this) | ||
| 91 | + } | ||
| 92 | + | ||
| 78 | EmitterUtils.receiveEvent(EmitterEventId.AUDIO_CHANGE_STATUS, (status) => { | 93 | EmitterUtils.receiveEvent(EmitterEventId.AUDIO_CHANGE_STATUS, (status) => { |
| 79 | Logger.debug(TAG, "接收音频播放状态 = " + status); | 94 | Logger.debug(TAG, "接收音频播放状态 = " + status); |
| 80 | if (!this.isPause) { | 95 | if (!this.isPause) { |
| @@ -101,50 +116,7 @@ export struct WdWebLocalComponent { | @@ -101,50 +116,7 @@ export struct WdWebLocalComponent { | ||
| 101 | 116 | ||
| 102 | Row() { | 117 | Row() { |
| 103 | RelativeContainer() { | 118 | RelativeContainer() { |
| 104 | - Web({ | ||
| 105 | - src: this.webResource, | ||
| 106 | - controller: this.webviewControl, | ||
| 107 | - renderMode: RenderMode.SYNC_RENDER | ||
| 108 | - })// Web({ src: this.webResource, controller: this.webviewControl }) | ||
| 109 | - .backgroundColor(Color.White) | ||
| 110 | - .domStorageAccess(true) | ||
| 111 | - .databaseAccess(true) | ||
| 112 | - .javaScriptAccess(true) | ||
| 113 | - .imageAccess(true) | ||
| 114 | - .mixedMode(MixedMode.All) | ||
| 115 | - .onlineImageAccess(true) | ||
| 116 | - .cacheMode(this.mode) | ||
| 117 | - // .enableNativeEmbedMode(true) | ||
| 118 | - // .layoutMode(WebLayoutMode.FIT_CONTENT) | ||
| 119 | - .nestedScroll({ | ||
| 120 | - scrollForward: NestedScrollMode.SELF_FIRST, | ||
| 121 | - scrollBackward: NestedScrollMode.PARENT_FIRST | ||
| 122 | - }) | ||
| 123 | - .width('100%') | ||
| 124 | - .height(this.webHeight) | ||
| 125 | - .onPageBegin((event) => { | ||
| 126 | - this.onPageBegin(event?.url); | ||
| 127 | - }) | ||
| 128 | - .onPageEnd((event) => { | ||
| 129 | - this.onPageEnd(event?.url) | ||
| 130 | - }) | ||
| 131 | - .onLoadIntercept((event) => { | ||
| 132 | - let url: string = event.data.getRequestUrl().toString() | ||
| 133 | - url = url.replace("%(?![0-9a-fA-F]{2})", "%25") | ||
| 134 | - .replace("\\+", "%2B"); | ||
| 135 | - url = decodeURIComponent(url) | ||
| 136 | - Logger.debug(TAG, 'Web onLoadIntercept url: ' + url); | ||
| 137 | - if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) { | ||
| 138 | - this.webviewControl.handlerReturnData(url) | ||
| 139 | - return true | ||
| 140 | - } | ||
| 141 | - if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { | ||
| 142 | - // this.webviewControl.flushMessageQueue() | ||
| 143 | - this.webviewControl.receiveDataFromH5(url) | ||
| 144 | - return true | ||
| 145 | - } | ||
| 146 | - return this.onLoadIntercept(event.data.getRequestUrl().toString()); | ||
| 147 | - }) | 119 | + NodeContainer(WebPoolManager.sharedInstance().getWebNodeController(this.webPoolTargetId)) |
| 148 | .id('web') | 120 | .id('web') |
| 149 | .alignRules({ | 121 | .alignRules({ |
| 150 | top: { anchor: "__container__", align: VerticalAlign.Top }, | 122 | top: { anchor: "__container__", align: VerticalAlign.Top }, |
| @@ -171,20 +143,6 @@ export struct WdWebLocalComponent { | @@ -171,20 +143,6 @@ export struct WdWebLocalComponent { | ||
| 171 | } | 143 | } |
| 172 | } | 144 | } |
| 173 | 145 | ||
| 174 | - private registerHandlers(): void { | ||
| 175 | - // 注册h5调用js相关 | ||
| 176 | - for (let i = 0; i < H5CallNativeType.JsCallTypeList.length; i++) { | ||
| 177 | - let handleName = H5CallNativeType.JsCallTypeList[i]; | ||
| 178 | - let handle = (data: Message, f: Callback) => { | ||
| 179 | - Logger.debug('registerHandlers handlerName: ' + JSON.stringify(data.data)) | ||
| 180 | - this.setCurrentPageOperate8(data) | ||
| 181 | - this.setCurrentPageOperate9(data) | ||
| 182 | - this.defaultPerformJSCallNative(data, f) | ||
| 183 | - }; | ||
| 184 | - this.webviewControl.registerHandler(handleName, { handle: handle }); | ||
| 185 | - } | ||
| 186 | - } | ||
| 187 | - | ||
| 188 | //webview 高度设置 | 146 | //webview 高度设置 |
| 189 | private setCurrentPageOperate8: (data: Message) => void = (data) => { | 147 | private setCurrentPageOperate8: (data: Message) => void = (data) => { |
| 190 | 148 | ||
| @@ -198,6 +156,8 @@ export struct WdWebLocalComponent { | @@ -198,6 +156,8 @@ export struct WdWebLocalComponent { | ||
| 198 | { | 156 | { |
| 199 | this.webHeight = Number(data?.data?.webViewHeight) || '100%' | 157 | this.webHeight = Number(data?.data?.webViewHeight) || '100%' |
| 200 | } | 158 | } |
| 159 | + | ||
| 160 | + WebPoolManager.sharedInstance().getWebNodeController(this.webPoolTargetId)?.updateWebHeight(this.webHeight) | ||
| 201 | } | 161 | } |
| 202 | } | 162 | } |
| 203 | // 暂停音频悬浮窗 | 163 | // 暂停音频悬浮窗 |
| @@ -228,29 +188,18 @@ export struct WdWebLocalComponent { | @@ -228,29 +188,18 @@ export struct WdWebLocalComponent { | ||
| 228 | /** | 188 | /** |
| 229 | * 默认【CallNative】逻辑处理 | 189 | * 默认【CallNative】逻辑处理 |
| 230 | */ | 190 | */ |
| 231 | - private defaultPerformJSCallNative: (data: Message, f: Callback) => void = (data: Message, f: Callback) => { | 191 | + private currentPageOperate: (data: Message, f: Callback) => void = (data: Message, f: Callback) => { |
| 192 | + this.setCurrentPageOperate8(data) | ||
| 193 | + this.setCurrentPageOperate9(data) | ||
| 232 | performJSCallNative(data, f) | 194 | performJSCallNative(data, f) |
| 233 | } | 195 | } |
| 234 | 196 | ||
| 235 | - onPageBegin: (url?: string) => void = () => { | ||
| 236 | - Logger.debug(TAG, 'onPageBegin'); | ||
| 237 | - this.registerHandlers(); | ||
| 238 | - //有时序问题 必须延时执行 | ||
| 239 | - setTimeout(() => { | ||
| 240 | - BridgeUtil.webViewLoadLocalJs(getContext(this), this.webviewControl) | ||
| 241 | - }, 200) | ||
| 242 | - } | ||
| 243 | onPageEnd: (url?: string) => void = () => { | 197 | onPageEnd: (url?: string) => void = () => { |
| 244 | Logger.debug(TAG, 'onPageEnd'); | 198 | Logger.debug(TAG, 'onPageEnd'); |
| 245 | this.onWebPrepared() | 199 | this.onWebPrepared() |
| 246 | this.isPageEnd = true | 200 | this.isPageEnd = true |
| 247 | } | 201 | } |
| 248 | 202 | ||
| 249 | - onLoadIntercept: (url?: string) => boolean = () => { | ||
| 250 | - Logger.debug(TAG, 'onLoadIntercept return false'); | ||
| 251 | - return false | ||
| 252 | - } | ||
| 253 | - | ||
| 254 | onReloadStateChanged() { | 203 | onReloadStateChanged() { |
| 255 | Logger.info(TAG, `onReloadStateChanged:::refresh, this.reload: ${this.reload}`); | 204 | Logger.info(TAG, `onReloadStateChanged:::refresh, this.reload: ${this.reload}`); |
| 256 | if (this.reload > 0) { | 205 | if (this.reload > 0) { |
| 1 | +import { BridgeUtil, BridgeWebViewControl, Callback } from "wdJsBridge"; | ||
| 2 | +import { Message } from "wdJsBridge/src/main/ets/bean/Message"; | ||
| 3 | +import { Logger } from "wdKit"; | ||
| 4 | +import { H5CallNativeType } from "../pages/H5CallNativeType"; | ||
| 5 | +import { WebEvents } from "./WebLocalPool"; | ||
| 6 | + | ||
| 7 | +const TAG = 'WebArticleEventHandler' | ||
| 8 | + | ||
| 9 | +export class WebArticleEventHandler implements WebEvents { | ||
| 10 | + webviewControl?: BridgeWebViewControl | ||
| 11 | + | ||
| 12 | + pageLoadEnd: boolean = false | ||
| 13 | + pageEndBlock?:(event: OnPageEndEvent) => void | ||
| 14 | + | ||
| 15 | + currentPageOperateBlock?: (data: Message, f: Callback) => void | ||
| 16 | + | ||
| 17 | + constructor(webviewControl: BridgeWebViewControl) { | ||
| 18 | + this.webviewControl = webviewControl | ||
| 19 | + } | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + //MARK: ---- | ||
| 23 | + onPrepareForReuse(): void { | ||
| 24 | + | ||
| 25 | + } | ||
| 26 | + | ||
| 27 | + onPageBegin(event: OnPageBeginEvent): void { | ||
| 28 | + Logger.debug(TAG, 'onPageBegin'); | ||
| 29 | + this.registerHandlers(); | ||
| 30 | + //有时序问题 必须延时执行 | ||
| 31 | + setTimeout(() => { | ||
| 32 | + BridgeUtil.webViewLoadLocalJs(getContext(this), this.webviewControl!) | ||
| 33 | + }, 200) | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + onPageEnd(event: OnPageEndEvent): void { | ||
| 37 | + Logger.debug(TAG, 'onPageEnd'); | ||
| 38 | + this.pageLoadEnd = true | ||
| 39 | + if (this.pageEndBlock) { | ||
| 40 | + this.pageEndBlock(event) | ||
| 41 | + } | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + onLoadIntercept(event: OnLoadInterceptEvent): boolean { | ||
| 45 | + let url: string = event.data.getRequestUrl().toString() | ||
| 46 | + url = url.replace("%(?![0-9a-fA-F]{2})", "%25") | ||
| 47 | + .replace("\\+", "%2B"); | ||
| 48 | + url = decodeURIComponent(url) | ||
| 49 | + Logger.debug(TAG, 'Web onLoadIntercept url: ' + url); | ||
| 50 | + if (url.startsWith(BridgeUtil.YY_RETURN_DATA)) { | ||
| 51 | + this.webviewControl?.handlerReturnData(url) | ||
| 52 | + return true | ||
| 53 | + } | ||
| 54 | + if (url.startsWith(BridgeUtil.YY_OVERRIDE_SCHEMA)) { | ||
| 55 | + // this.webviewControl.flushMessageQueue() | ||
| 56 | + this.webviewControl?.receiveDataFromH5(url) | ||
| 57 | + return true | ||
| 58 | + } | ||
| 59 | + return false; | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + private registerHandlers(): void { | ||
| 63 | + // 注册h5调用js相关 | ||
| 64 | + for (let i = 0; i < H5CallNativeType.JsCallTypeList.length; i++) { | ||
| 65 | + let handleName = H5CallNativeType.JsCallTypeList[i]; | ||
| 66 | + let handle = (data: Message, f: Callback) => { | ||
| 67 | + Logger.debug('registerHandlers handlerName: ' + JSON.stringify(data.data)) | ||
| 68 | + if (this.currentPageOperateBlock) { | ||
| 69 | + this.currentPageOperateBlock(data, f) | ||
| 70 | + } | ||
| 71 | + }; | ||
| 72 | + this.webviewControl?.registerHandler(handleName, { handle: handle }); | ||
| 73 | + } | ||
| 74 | + } | ||
| 75 | + | ||
| 76 | +} |
| 1 | +import { UIContext } from '@ohos.arkui.UIContext'; | ||
| 2 | +import { NodeController, BuilderNode, FrameNode } from '@ohos.arkui.node'; | ||
| 3 | +import { BusinessError } from '@ohos.base'; | ||
| 4 | +import { WebNetErrorList } from '@ohos.web.netErrorList'; | ||
| 5 | +import { Logger } from 'wdKit'; | ||
| 6 | + | ||
| 7 | +// @Builder中为动态组件的具体组件内容 | ||
| 8 | +// WebViewPoolData为入参封装类 | ||
| 9 | +class WebViewPoolData { | ||
| 10 | + target: string = '' | ||
| 11 | + webOption: WebOptions = { } as WebOptions | ||
| 12 | + webEvents?: WebEvents = undefined | ||
| 13 | + webHeight: Length = 0 | ||
| 14 | + | ||
| 15 | + constructor(webOption: WebOptions, target: string, webHeight: Length, webEvent?:WebEvents) { | ||
| 16 | + this.webOption = webOption | ||
| 17 | + this.webEvents = webEvent | ||
| 18 | + this.target = target | ||
| 19 | + this.webHeight = webHeight | ||
| 20 | + } | ||
| 21 | +} | ||
| 22 | + | ||
| 23 | +const TAG = 'WebViewPool'; | ||
| 24 | + | ||
| 25 | +// 影响web组件加载的网络错误码 | ||
| 26 | +class webLoadFail { | ||
| 27 | + static ERR_TIMED_OUT = WebNetErrorList.ERR_TIMED_OUT; | ||
| 28 | + static ERR_NETWORK_CHANGED = WebNetErrorList.ERR_NETWORK_CHANGED; | ||
| 29 | +} | ||
| 30 | + | ||
| 31 | +// 不需要重试刷新的网络错误码 | ||
| 32 | +class excludeRetryLoad { | ||
| 33 | + static ERR_FAILED = WebNetErrorList.ERR_FAILED; | ||
| 34 | +} | ||
| 35 | + | ||
| 36 | +function isErrorCode(code: string) { | ||
| 37 | + return Object.keys(webLoadFail).includes(code); | ||
| 38 | +} | ||
| 39 | + | ||
| 40 | +@Component | ||
| 41 | +export struct DynamicWebView { | ||
| 42 | + target: string = ''; | ||
| 43 | + private webOption: WebOptions = { } as WebOptions | ||
| 44 | + private webEvents?: WebEvents = undefined | ||
| 45 | + @Prop webHeight: Length | ||
| 46 | + | ||
| 47 | + aboutToAppear(): void { | ||
| 48 | + | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + build() { | ||
| 52 | + Row() { | ||
| 53 | + Web(this.webOption) | ||
| 54 | + .backgroundColor(Color.White) | ||
| 55 | + .domStorageAccess(true) | ||
| 56 | + .databaseAccess(true) | ||
| 57 | + .javaScriptAccess(true) | ||
| 58 | + .imageAccess(true) | ||
| 59 | + .mixedMode(MixedMode.All) | ||
| 60 | + .onlineImageAccess(true) | ||
| 61 | + .nestedScroll({ | ||
| 62 | + scrollForward: NestedScrollMode.SELF_FIRST, | ||
| 63 | + scrollBackward: NestedScrollMode.PARENT_FIRST | ||
| 64 | + }) | ||
| 65 | + .width('100%') | ||
| 66 | + .height('100%') | ||
| 67 | + .onPageBegin((event) => { | ||
| 68 | + this.webEvents?.onPageBegin(event) | ||
| 69 | + }) | ||
| 70 | + .onPageEnd((event) => { | ||
| 71 | + this.webEvents?.onPageEnd(event) | ||
| 72 | + }) | ||
| 73 | + .onLoadIntercept((event) => { | ||
| 74 | + if (!this.webEvents) { | ||
| 75 | + return false | ||
| 76 | + } | ||
| 77 | + return this.webEvents?.onLoadIntercept(event); | ||
| 78 | + }) | ||
| 79 | + }.width('100%') | ||
| 80 | + .height(this.webHeight) | ||
| 81 | + } | ||
| 82 | +} | ||
| 83 | + | ||
| 84 | +@Builder | ||
| 85 | +export function webBuilder(data: WebViewPoolData) { | ||
| 86 | + DynamicWebView({ | ||
| 87 | + webOption: data.webOption, | ||
| 88 | + target: data.target, | ||
| 89 | + webEvents: data.webEvents, | ||
| 90 | + webHeight: data.webHeight | ||
| 91 | + }) | ||
| 92 | +} | ||
| 93 | + | ||
| 94 | +export class WebNodeController extends NodeController { | ||
| 95 | + private rootNode: BuilderNode<[WebViewPoolData]> | null = null; | ||
| 96 | + public webOption: WebOptions = { } as WebOptions | ||
| 97 | + public webEvents?: WebEvents = undefined | ||
| 98 | + private webHeight: Length = 0 | ||
| 99 | + private target: string = ''; | ||
| 100 | + public webNodeInTree:boolean = false; | ||
| 101 | + | ||
| 102 | + constructor(webOption: WebOptions, target: string, webHeight: Length, webEvent?:WebEvents) { | ||
| 103 | + super(); | ||
| 104 | + this.webOption = webOption | ||
| 105 | + this.webEvents = webEvent | ||
| 106 | + this.target = target | ||
| 107 | + this.webHeight = webHeight | ||
| 108 | + } | ||
| 109 | + | ||
| 110 | + aboutToAppear(): void { | ||
| 111 | + this.webNodeInTree = true; | ||
| 112 | + Logger.debug(TAG, `WebNodeController aboutToAppear target: ${this.target} ==> url:${this.webOption.src}`); | ||
| 113 | + } | ||
| 114 | + | ||
| 115 | + aboutToDisappear(): void { | ||
| 116 | + try { | ||
| 117 | + this.webNodeInTree = false; | ||
| 118 | + // 下树时给commonWebView加载空白页面 | ||
| 119 | + // if (!getNWeb(this.target)) { | ||
| 120 | + // this.webOption.controller?.loadData( | ||
| 121 | + // "<html><body bgcolor=\"#18181A\"></body></html>", | ||
| 122 | + // "text/html", | ||
| 123 | + // "UTF-8" | ||
| 124 | + // ); | ||
| 125 | + // } | ||
| 126 | + Logger.debug(TAG, `WebNodeController aboutToDisappear target: ${this.target} ==> url:${this.webOption.src}`); | ||
| 127 | + } catch (error) { | ||
| 128 | + Logger.error(TAG,`ErrorCode: ${(error as BusinessError).code}, Message: ${(error as BusinessError).message}`); | ||
| 129 | + } | ||
| 130 | + } | ||
| 131 | + | ||
| 132 | + makeNode(context: UIContext): FrameNode | null { | ||
| 133 | + if (!context) { | ||
| 134 | + Logger.error(TAG, " uicontext is undifined : " + (context === undefined)); | ||
| 135 | + } | ||
| 136 | + if (this.rootNode != null) { | ||
| 137 | + // 返回FrameNode节点 | ||
| 138 | + return this.rootNode.getFrameNode(); | ||
| 139 | + } | ||
| 140 | + // 返回null控制动态组件脱离绑定节点 | ||
| 141 | + return null; | ||
| 142 | + } | ||
| 143 | + | ||
| 144 | + // 此函数为自定义函数,可作为初始化函数使用 | ||
| 145 | + // 通过UIContext初始化BuilderNode,再通过BuilderNode中的build接口初始化@Builder中的内容 | ||
| 146 | + initWeb(uiContext: UIContext) { | ||
| 147 | + if (this.rootNode != null) { | ||
| 148 | + return; | ||
| 149 | + } | ||
| 150 | + this.rootNode = new BuilderNode(uiContext); | ||
| 151 | + this.rootNode.build(wrapBuilder<[WebViewPoolData]>(webBuilder), | ||
| 152 | + new WebViewPoolData(this.webOption, this.target, this.webHeight, this.webEvents)); | ||
| 153 | + } | ||
| 154 | + | ||
| 155 | + prepareForReuse() { | ||
| 156 | + this.webEvents?.onPrepareForReuse() | ||
| 157 | + } | ||
| 158 | + | ||
| 159 | + destoryWeb() { | ||
| 160 | + this.rootNode?.dispose() | ||
| 161 | + this.rootNode = null | ||
| 162 | + } | ||
| 163 | + | ||
| 164 | + updateWebHeight(webHeight: Length) { | ||
| 165 | + try { | ||
| 166 | + if (this.rootNode !== null) { | ||
| 167 | + this.webHeight = webHeight | ||
| 168 | + this.rootNode.update(new WebViewPoolData(this.webOption, this.target, this.webHeight, this.webEvents)); | ||
| 169 | + } | ||
| 170 | + } catch (err) { | ||
| 171 | + Logger.warn(TAG, `commonWebView update error : ${err}`); | ||
| 172 | + } | ||
| 173 | + } | ||
| 174 | + | ||
| 175 | + updateWebEvents(webEvent: WebEvents) { | ||
| 176 | + try { | ||
| 177 | + if (this.rootNode !== null) { | ||
| 178 | + this.webEvents = webEvent | ||
| 179 | + this.rootNode.update(new WebViewPoolData(this.webOption, this.target, this.webHeight, this.webEvents)); | ||
| 180 | + } | ||
| 181 | + } catch (err) { | ||
| 182 | + Logger.warn(TAG, `commonWebView update error : ${err}`); | ||
| 183 | + } | ||
| 184 | + } | ||
| 185 | + | ||
| 186 | + | ||
| 187 | + | ||
| 188 | + | ||
| 189 | +} | ||
| 190 | + | ||
| 191 | +export interface WebEvents { | ||
| 192 | + onPrepareForReuse(): void | ||
| 193 | + onPageBegin(event: OnPageBeginEvent): void; | ||
| 194 | + onPageEnd(event: OnPageEndEvent): void; | ||
| 195 | + onLoadIntercept(event: OnLoadInterceptEvent): boolean; | ||
| 196 | +} | ||
| 197 | + | ||
| 198 | +// 创建Map保存所需要的NodeController | ||
| 199 | +const nodeMap: Map<string, WebNodeControllerInfo> = new Map(); | ||
| 200 | + | ||
| 201 | +// NodeController内容和创建时间戳 | ||
| 202 | +class WebNodeControllerInfo { | ||
| 203 | + webNodeController?: WebNodeController; | ||
| 204 | + createTime: number = 0; | ||
| 205 | + | ||
| 206 | + constructor(webNodeController: WebNodeController, createTime: number) { | ||
| 207 | + this.webNodeController = webNodeController; | ||
| 208 | + this.createTime = createTime; | ||
| 209 | + } | ||
| 210 | +} | ||
| 211 | + | ||
| 212 | +// 自定义获取NodeController接口 | ||
| 213 | +export const getPoolWebNodeController = (target: string): WebNodeController | undefined => { | ||
| 214 | + return nodeMap.get(target)?.webNodeController; | ||
| 215 | +} | ||
| 216 | + | ||
| 217 | +// 自定义获取WebviewController接口 | ||
| 218 | +export const getPoolWebController = (target: string): WebviewController | undefined => { | ||
| 219 | + return nodeMap.get(target)?.webNodeController?.webOption.controller as WebviewController; | ||
| 220 | +} | ||
| 221 | + | ||
| 222 | +// 初始化需要UIContext 需在Ability获取 | ||
| 223 | +export const createReuseableWeb = (uiContext: UIContext, webOption: WebOptions, target: string, webHeight: Length, webEvent?:WebEvents) => { | ||
| 224 | + // 创建NodeController | ||
| 225 | + let baseNode = new WebNodeController(webOption, target, webHeight, webEvent); | ||
| 226 | + // 初始化自定义Web组件 | ||
| 227 | + baseNode.initWeb(uiContext); | ||
| 228 | + nodeMap.set(target, new WebNodeControllerInfo(baseNode, Date.now())); | ||
| 229 | +} | ||
| 230 | + | ||
| 231 | +export const destoryReuseableWeb = (target: string) => { | ||
| 232 | + if (nodeMap.get(target)) { | ||
| 233 | + getPoolWebNodeController(target)?.destoryWeb() | ||
| 234 | + nodeMap.delete(target) | ||
| 235 | + } | ||
| 236 | +} |
| 1 | +import { BridgeWebViewControl } from "wdJsBridge" | ||
| 2 | +import { AppUtils } from "wdKit" | ||
| 3 | +import { createReuseableWeb, destoryReuseableWeb, getPoolWebNodeController, WebNodeController } from "./WebLocalPool" | ||
| 4 | +import { util } from "@kit.ArkTS" | ||
| 5 | +import { WebArticleEventHandler } from "./WebArticleEventHandler" | ||
| 6 | + | ||
| 7 | +interface ArticleWebInterface { | ||
| 8 | + uiContext: UIContext, | ||
| 9 | + webSrc: string | Resource, | ||
| 10 | + webController: BridgeWebViewControl | ||
| 11 | + webArticleEventHandler: WebArticleEventHandler | ||
| 12 | +} | ||
| 13 | + | ||
| 14 | +export class WebPoolManager { | ||
| 15 | + | ||
| 16 | + private webTargets: string[] = [] | ||
| 17 | + | ||
| 18 | + private constructor() { | ||
| 19 | + } | ||
| 20 | + private static manager: WebPoolManager | ||
| 21 | + static sharedInstance() : WebPoolManager { | ||
| 22 | + if (!WebPoolManager.manager) { | ||
| 23 | + WebPoolManager.manager = new WebPoolManager() | ||
| 24 | + } | ||
| 25 | + return WebPoolManager.manager | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + createArticleWeb(param: ArticleWebInterface, taking?: boolean) : string { | ||
| 29 | + let target = util.generateRandomUUID() | ||
| 30 | + createReuseableWeb(param.uiContext, | ||
| 31 | + {src: param.webSrc, controller: param.webController}, | ||
| 32 | + target, | ||
| 33 | + 800, | ||
| 34 | + param.webArticleEventHandler | ||
| 35 | + ) | ||
| 36 | + if (taking != true) { | ||
| 37 | + this.webTargets.push(target) | ||
| 38 | + } | ||
| 39 | + return target | ||
| 40 | + } | ||
| 41 | + | ||
| 42 | + // 取一个可用的target,不存在则根据参数创建 | ||
| 43 | + // 当使用完成,需要调用recycleTarget() | ||
| 44 | + takeAvaiableArticleWebTarget(param: ArticleWebInterface): string { | ||
| 45 | + if (this.webTargets.length) { | ||
| 46 | + return this.webTargets.slice(0, 1).pop()! | ||
| 47 | + } | ||
| 48 | + return this.createArticleWeb(param, true) | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + // 回收target | ||
| 52 | + recycleTarget(target: string) { | ||
| 53 | + let nodeController = getPoolWebNodeController(target) | ||
| 54 | + if (!nodeController) { | ||
| 55 | + let index = this.webTargets.indexOf(target) | ||
| 56 | + if (index >= 0) { | ||
| 57 | + this.webTargets.splice(index, 1) | ||
| 58 | + } | ||
| 59 | + return | ||
| 60 | + } | ||
| 61 | + | ||
| 62 | + nodeController.prepareForReuse() | ||
| 63 | + this.webTargets.push(target) | ||
| 64 | + } | ||
| 65 | + | ||
| 66 | + // 撤底销毁 | ||
| 67 | + destoryTarget(target: string) { | ||
| 68 | + destoryReuseableWeb(target) | ||
| 69 | + let index = this.webTargets.indexOf(target) | ||
| 70 | + if (index >= 0) { | ||
| 71 | + this.webTargets.splice(index, 1) | ||
| 72 | + } | ||
| 73 | + } | ||
| 74 | + | ||
| 75 | + getWebNodeController(target: string) : WebNodeController | undefined { | ||
| 76 | + return getPoolWebNodeController(target) | ||
| 77 | + } | ||
| 78 | + getWebController(target: string) : BridgeWebViewControl | undefined { | ||
| 79 | + return getPoolWebNodeController(target)?.webOption.controller as BridgeWebViewControl | ||
| 80 | + } | ||
| 81 | + getWebEventHandler(target: string) : WebArticleEventHandler | undefined { | ||
| 82 | + return getPoolWebNodeController(target)?.webEvents as WebArticleEventHandler | ||
| 83 | + } | ||
| 84 | + | ||
| 85 | + | ||
| 86 | +} |
| @@ -8,7 +8,7 @@ import { | @@ -8,7 +8,7 @@ import { | ||
| 8 | } from 'wdBean'; | 8 | } from 'wdBean'; |
| 9 | import { Logger, SPHelper, NetworkUtil, DisplayUtils } from 'wdKit'; | 9 | import { Logger, SPHelper, NetworkUtil, DisplayUtils } from 'wdKit'; |
| 10 | import { SpConstants } from 'wdConstant'; | 10 | import { SpConstants } from 'wdConstant'; |
| 11 | -import { WdWebLocalComponent } from 'wdWebComponent'; | 11 | +import { WdWebLocalComponent, WebArticleEventHandler, WebPoolManager } from 'wdWebComponent'; |
| 12 | import { NativeCallH5Type } from 'wdWebComponent/src/main/ets/pages/NativeCallH5Type'; | 12 | import { NativeCallH5Type } from 'wdWebComponent/src/main/ets/pages/NativeCallH5Type'; |
| 13 | import { BridgeWebViewControl } from 'wdJsBridge/Index'; | 13 | import { BridgeWebViewControl } from 'wdJsBridge/Index'; |
| 14 | 14 | ||
| @@ -19,6 +19,7 @@ export struct ImageAndTextWebComponent { | @@ -19,6 +19,7 @@ export struct ImageAndTextWebComponent { | ||
| 19 | @State reload: number = 0; | 19 | @State reload: number = 0; |
| 20 | @Link isPageEnd: boolean | 20 | @Link isPageEnd: boolean |
| 21 | @Prop @Watch('onDetailDataUpdated') contentDetailData: ContentDetailDTO [] = [] as ContentDetailDTO [] | 21 | @Prop @Watch('onDetailDataUpdated') contentDetailData: ContentDetailDTO [] = [] as ContentDetailDTO [] |
| 22 | + webPoolTargetId: string = '' | ||
| 22 | webviewControl: BridgeWebViewControl = new BridgeWebViewControl() | 23 | webviewControl: BridgeWebViewControl = new BridgeWebViewControl() |
| 23 | private h5ReceiveAppData: H5ReceiveDetailBean = { dataSource: '2' } as H5ReceiveDetailBean | 24 | private h5ReceiveAppData: H5ReceiveDetailBean = { dataSource: '2' } as H5ReceiveDetailBean |
| 24 | private webPrepared = false; | 25 | private webPrepared = false; |
| @@ -95,11 +96,20 @@ export struct ImageAndTextWebComponent { | @@ -95,11 +96,20 @@ export struct ImageAndTextWebComponent { | ||
| 95 | 96 | ||
| 96 | aboutToAppear(): void { | 97 | aboutToAppear(): void { |
| 97 | Logger.debug(TAG, 'H5模板加载控件 aboutToAppear'); | 98 | Logger.debug(TAG, 'H5模板加载控件 aboutToAppear'); |
| 99 | + | ||
| 100 | + this.webPoolTargetId = WebPoolManager.sharedInstance().takeAvaiableArticleWebTarget({ | ||
| 101 | + uiContext: this.getUIContext(), | ||
| 102 | + webSrc: $rawfile('apph5/index.html'), | ||
| 103 | + webController: this.webviewControl, | ||
| 104 | + webArticleEventHandler: new WebArticleEventHandler(this.webviewControl) | ||
| 105 | + }) | ||
| 106 | + this.webviewControl = WebPoolManager.sharedInstance().getWebController(this.webPoolTargetId)! | ||
| 98 | } | 107 | } |
| 99 | 108 | ||
| 100 | build() { | 109 | build() { |
| 101 | Column() { | 110 | Column() { |
| 102 | WdWebLocalComponent({ | 111 | WdWebLocalComponent({ |
| 112 | + webPoolTargetId: this.webPoolTargetId, | ||
| 103 | webviewControl: this.webviewControl, | 113 | webviewControl: this.webviewControl, |
| 104 | reload:this.reload, | 114 | reload:this.reload, |
| 105 | webResource: $rawfile('apph5/index.html'), | 115 | webResource: $rawfile('apph5/index.html'), |
-
Please register or login to post a comment