<

{{title}}

{{file.summary.functions.pct}}%

Functions
{{file.summary.functions.covered}}/{{file.summary.functions.total}}

{{file.summary.branches.pct}}%

Branches
{{file.summary.branches.covered}}/{{file.summary.branches.total}}

{{file.summary.lines.pct}}%

Lines
{{file.summary.lines.covered}}/{{file.summary.lines.total}}
          
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2x
2x
2x
1x
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9x
9x
9x
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2x
2x
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4x
4x
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4x
4x
4x
4x
4x
4x
4x
 
 
 
 
 
11x
11x
 
 
 
 
 
 
 
 
11x
11x
11x
11x
11x
 
 
 
 
 
 
 
                
import webview from '@ohos.web.webview'; 
import { UIContext } from '@ohos.arkui.UIContext'; 
import { SDK_VERSION } from '../SdkVersion'; 
import { WebHostModel } from './WebHostModel'; 
import { WebNodeController } from './TencentQianWebNode'; 
import { TencentQianWebViewHandlers, ScriptItemLike } from '../../TencentQianWebViewConfigurator'; 
import { WebSettingsApplier } from '../WebSettingsApplier'; 
import { NetworkStateInjector } from '../NetworkStateInjector'; 
 
/** 
 * 鸿蒙 WebView 离屏复用池(容量 = 1,**借还池**,与 Android 规格一致)。 
 * 
 * 基于官方「离线 Web 组件」:`WebNodeController` + `BuilderNode` 命令式创建 Web 组件(离屏、 
 * Hidden/Inactive、启动渲染进程),HEAVY 加载 `warmupUrl` 热网络缓存。真实页容器用 
 * `NodeContainer(nodeController)` 绑定复用,通过热替换 `WebHostModel.handlers` / `url` 切换为签署态。 
 * 
 * **生命周期(借还池,同 Android)**: 
 * - HEAVY `warm()`:离屏建组件 + load(warmupUrl);多次 prewarm 复用同一实例,绝不重复建。 
 * - `open()` → `obtain()`:借出(UA 配置匹配才复用),容器热替换 handlers/url 并 loadUrl 真实页。 
 * - 容器关闭 → `recycle()`:**不销毁**,`about:blank` + `clearHistory` 后放回池,待下次复用。 
 * - 客户主动 `release()`(或内存告警):才真正 `dispose` 销毁。 
 * 
 * **约束**:`BuilderNode` 需 `UIContext`(`prewarm(HEAVY, bizType, uiContext)` 传入); 
 * 每组件约 200MB,容量恒 1;`dispose` 前须先从 `NodeContainer` 解绑(`onUnbind`),否则白屏。 
 */ 
 
/** UA 签名:复用门控——预热 UA 配置需与 `open()` config UA 一致,否则不复用(避免 warmupUrl 缓存 miss)。 */ 
function uaSignatureOf(injectUA: boolean, uaExtra: string): string { 
  return (injectUA ? '1' : '0') + '|' + uaExtra; 
} 
 
/** 
 * 构造预热用最小 handlers:DEFAULTS settings + UA 注入 + no-op 事件。 
 * 真实页复用时会被 `Configurator.apply` 生成的真实 handlers 热替换(见 Page/Component)。 
 */ 
function buildPrewarmHandlers( 
  controller: webview.WebviewController, 
  injectUA: boolean, 
  uaExtra: string 
): TencentQianWebViewHandlers { 
  const injector: NetworkStateInjector = new NetworkStateInjector(); 
  const emptyScripts: Array<ScriptItemLike> = []; 
  const handlers: TencentQianWebViewHandlers = { 
    webSettings: WebSettingsApplier.DEFAULTS, 
    mixedMode: 'None', 
    documentStartScripts: emptyScripts, 
    networkStateInjector: injector, 
    onControllerAttached: (): void => { 
      try { 
        if (injectUA) { 
          const base: string = controller.getUserAgent(); 
          let ua: string = base + ' qianwv/' + SDK_VERSION; 
          if (uaExtra.length > 0) { 
            ua = ua + ' ' + uaExtra; 
          } 
          controller.setCustomUserAgent(ua); 
        } 
      } catch (_e) { 
        // best-effort 
      } 
      try { 
        injector.attach(controller); 
      } catch (_e) { 
        // ignore 
      } 
    }, 
    onLoadIntercept: (_event: ESObject): boolean => false, 
    onPageBegin: (_event: ESObject): void => {}, 
    onPageEnd: (_event: ESObject): void => {}, 
    onPermissionRequest: (_event: ESObject): void => {}, 
    onShowFileSelector: (_event: ESObject): boolean => false, 
    onAlert: (_event: ESObject): boolean => false, 
    onConfirm: (_event: ESObject): boolean => false, 
    onPrompt: (_event: ESObject): boolean => false, 
    onDownloadStart: (_event: ESObject): void => {}, 
    onErrorReceive: (_event: ESObject): void => {}, 
    onHttpErrorReceive: (_event: ESObject): void => {}, 
    onSslErrorEventReceive: (_event: ESObject): void => {}, 
    // 预热态 / 归还态守卫:此时节点未绑定在可见容器上,渲染进程崩溃直接销毁并作废池实例 
    // (避免"渲染进程已死的僵尸实例"被后续 obtain 复用导致持久白屏)。对齐 Android PoolGuardClient。 
    // 借出态不走这里:借出后 handlers 被 Configurator 真实 handlers 热替换(其 onRenderExited 仅发事件、 
    // 不作废池),作废由统一 Builder 的 onPoolInvalidateRequest → unpoolBorrowed() 完成 
    // (只脱池、不销毁正在显示的活节点)。 
    onRenderExited: (_event: ESObject): void => { 
      HarmonyWebViewPool.invalidate(); 
    }, 
    onProgressChange: (_event: ESObject): void => {} 
  }; 
  return handlers; 
} 
 
/** 
 * 鸿蒙离屏借还池(单例,容量 1)。 
 */ 
export class HarmonyWebViewPool { 
  private static node: WebNodeController | null = null; 
  private static warmedUrl: string = ''; 
  /** 是否已被容器借出(借出中不可再 obtain)。 */ 
  private static inUse: boolean = false; 
  /** 预热时的 UA 签名,供复用门控比对。 */ 
  private static uaSig: string = ''; 
  /** 预热时的原始 UA 配置,供归还时重建预热守卫 handlers(避免从 uaSig 反解)。 */ 
  private static warmedInjectUA: boolean = false; 
  private static warmedUaExtra: string = ''; 
  private static enabled: boolean = true; 
 
  public static setEnabled(value: boolean): void { 
    HarmonyWebViewPool.enabled = value; 
    if (!value) { 
      HarmonyWebViewPool.release(); 
    } 
  } 
 
  /** 
   * HEAVY:离屏建 Web 组件并加载 `warmupUrl`。多次 prewarm 复用同一实例(按需切 URL),绝不重复建。 
   */ 
  public static warm(warmupUrl: string, uiContext: UIContext, injectUA: boolean, uaExtra: string): void { 
    if (!HarmonyWebViewPool.enabled || warmupUrl.length === 0) { 
      return; 
    } 
    try { 
      if (HarmonyWebViewPool.node === null) { 
        const controller: webview.WebviewController = new webview.WebviewController(); 
        const model: WebHostModel = new WebHostModel(controller); 
        model.url = warmupUrl; 
        model.handlers = buildPrewarmHandlers(controller, injectUA, uaExtra); 
        const ctrl: WebNodeController = new WebNodeController(); 
        ctrl.init(uiContext, model); 
        HarmonyWebViewPool.node = ctrl; 
        HarmonyWebViewPool.uaSig = uaSignatureOf(injectUA, uaExtra); 
        HarmonyWebViewPool.warmedInjectUA = injectUA; 
        HarmonyWebViewPool.warmedUaExtra = uaExtra; 
      } else if (warmupUrl !== HarmonyWebViewPool.warmedUrl && !HarmonyWebViewPool.inUse) { 
        const model: WebHostModel | null = HarmonyWebViewPool.node.getModel(); 
        if (model !== null) { 
          try { 
            model.controller.loadUrl(warmupUrl); 
          } catch (_e) { 
            // best-effort 
          } 
        } 
      } 
      HarmonyWebViewPool.warmedUrl = warmupUrl; 
    } catch (_e) { 
      // best-effort:预热失败绝不影响主流程 
    } 
  } 
 
  /** 
   * 容器复用:取出池 node(未借出、已就绪、且 UA 配置匹配时返回;否则 null → 容器自建)。 
   */ 
  public static obtain(injectUA: boolean, uaExtra: string): WebNodeController | null { 
    if (!HarmonyWebViewPool.enabled || HarmonyWebViewPool.node === null || HarmonyWebViewPool.inUse) { 
      return null; 
    } 
    if (HarmonyWebViewPool.uaSig !== uaSignatureOf(injectUA, uaExtra)) { 
      return null; 
    } 
    HarmonyWebViewPool.inUse = true; 
    return HarmonyWebViewPool.node; 
  } 
 
  /** 
   * 归还(借还池,**不销毁**):清空内容 + 清历史,保留实例待下次复用(同 Android)。 
   * 容器须已先从 `NodeContainer` 解绑。 
   * 
   * **归还态守卫(对齐 Android QianFallbackWebViewClient)**:把 `model.handlers` 复位为 
   * 预热守卫 handlers,其 `onRenderExited` 会作废池——覆盖"归还后 ~ 下次借出前"窗口内渲染 
   * 进程崩溃的场景,避免僵尸实例被后续 `obtain` 复用。同时重置 `model.fromPool=false`。 
   */ 
  public static recycle(): void { 
    if (HarmonyWebViewPool.node === null) { 
      return; 
    } 
    const model: WebHostModel | null = HarmonyWebViewPool.node.getModel(); 
    if (model !== null) { 
      try { 
        model.controller.loadUrl('about:blank'); 
        model.controller.clearHistory(); 
      } catch (_e) { 
        // ignore 
      } 
      // 复位为预热态守卫 handlers:归还态渲染进程崩溃时作废池(三态闭环)。 
      // 直接复用预热时保存的原始 UA 配置,避免从 uaSig 反解。 
      model.handlers = buildPrewarmHandlers( 
        model.controller, HarmonyWebViewPool.warmedInjectUA, HarmonyWebViewPool.warmedUaExtra); 
      model.fromPool = false; 
    } 
    HarmonyWebViewPool.warmedUrl = 'about:blank'; 
    HarmonyWebViewPool.inUse = false; 
  } 
 
  public static isInUse(): boolean { 
    return HarmonyWebViewPool.inUse; 
  } 
 
  /** 
   * 内存压力回收:**仅在实例未被借出(`!inUse`)且存在时**才 dispose 销毁, 
   * 避免打断正在使用的签署容器。 
   * 
   * 借出中(`inUse === true`)则跳过——等容器关闭 `recycle()` 归还回空闲态后, 
   * 下次内存告警再回收。 
   * 
   * @returns 是否真正释放了实例(供上层据此决定是否重置去重状态,使后续 prewarm 可重新预热) 
   */ 
  public static releaseIfIdle(): boolean { 
    if (HarmonyWebViewPool.inUse || HarmonyWebViewPool.node === null) { 
      return false; 
    } 
    HarmonyWebViewPool.release(); 
    return true; 
  } 
 
  /** 
   * 作废池实例(**预热态 / 归还态**渲染进程崩溃场景,对齐 Android `invalidateIfPooled`)。 
   * 
   * 此时 node 未绑定在任何可见容器上,销毁当前池 node 并清空全部池状态是安全的, 
   * 避免"渲染进程已死的僵尸实例"被后续 `obtain` 反复借出导致持久白屏。容量恒 1, 
   * 故直接作废当前实例即可;下次 `obtain` 返回 null → 容器自建。 
   * 
   * ⚠️ **借出态**(node 正绑在容器 `NodeContainer` 上显示)崩溃时**不要**用本方法: 
   * 销毁正在显示、且本可原地 `refresh()` 恢复的活节点会导致白屏/闭页,请用 {@link unpoolBorrowed}。 
   */ 
  public static invalidate(): void { 
    HarmonyWebViewPool.release(); 
  } 
 
  /** 
   * 借出态渲染进程崩溃专用:**只脱池、不销毁正在显示的活节点** 
   * (对齐 Android `invalidateIfPooled`——只清池槽位、不销毁 Activity 正用的 WebView)。 
   * 
   * 借出后 node 仍绑在容器 `NodeContainer` 上,`WebviewController` 渲染进程崩溃后依旧有效, 
   * 容器会在同一 controller 上 `refresh()` 原地恢复(鸿蒙无需与界面节点分离 / 重建)。 
   * 故这里仅清空池账本,使该崩溃实例**不再被 `obtain` 借出、也不再被 `recycle` 收回**; 
   * 活节点由容器继续持有,最终在容器关闭(`aboutToDisappear`,`fromPool=false` → `disposeNode()`) 
   * 时销毁,生命周期闭环不变。下次 `obtain` 因 `node=null` 返回 null → 新 `open()` 自建新实例,不复用僵尸。 
   * 
   * **刻意不做** `disposeNode()` / `cancelPendingRecover()`——避免打断正在进行的原地崩溃恢复。 
   */ 
  public static unpoolBorrowed(): void { 
    HarmonyWebViewPool.node = null; 
    HarmonyWebViewPool.warmedUrl = ''; 
    HarmonyWebViewPool.inUse = false; 
    HarmonyWebViewPool.uaSig = ''; 
    HarmonyWebViewPool.warmedInjectUA = false; 
    HarmonyWebViewPool.warmedUaExtra = ''; 
  } 
 
  /** 
   * 客户主动释放池实例(销毁)。签署流程结束、不再需要时调用。 
   */ 
  public static release(): void { 
    if (HarmonyWebViewPool.node !== null) { 
      // 取消可能挂起的延迟恢复,避免回调访问即将 dispose 的 controller(野指针)。 
      const model: WebHostModel | null = HarmonyWebViewPool.node.getModel(); 
      if (model !== null) { 
        model.cancelPendingRecover(); 
      } 
      HarmonyWebViewPool.node.disposeNode(); 
      HarmonyWebViewPool.node = null; 
    } 
    HarmonyWebViewPool.warmedUrl = ''; 
    HarmonyWebViewPool.inUse = false; 
    HarmonyWebViewPool.uaSig = ''; 
    HarmonyWebViewPool.warmedInjectUA = false; 
    HarmonyWebViewPool.warmedUaExtra = ''; 
  } 
 
  // ArkTS 类禁止实例化 
  private constructor() {} 
} 

              
Code coverage generated by bjc at {{date}}