典型报错/现象:

  • Uncaught TypeError: Cannot read properties of undefined (reading 'toast')
  • window.gameNative 长时间为 undefined
  • 偶现可用、偶现不可用(时序问题)

GamePlugin 实现,专门解决 JS Proxy 注入失败 类问题,帮助你系统定位 window.gameNative 无法使用的根因。


1. 调用链与注入时机总览

在项目中,window.gameNative.toast() 的可用性取决于三个环节:

  1. ArkTS 插件 是否正确注册 JS Proxy。
  2. 注入时机 是否覆盖了 WebView 生命周期关键点。
  3. Web 端调用 是否有降级与重试策略。

1.1 调用与注入的整体时序

WebviewController GamePlugin(ArkTS) window.gameNative Web(app.js) WebviewController GamePlugin(ArkTS) window.gameNative Web(app.js) 初始化阶段 运行阶段 registerJavaScriptProxy(gameNative, 'gameNative') 注入 window.gameNative gameNative.toast('新纪录 2048') JsGameNative.toast(message)

时序相关特性

  • 如果 WebPlugin 更早执行,就会出现一段时间 window.gameNative === undefined
  • 若只在某一个时机注册 Proxy(比如 initialize),一旦错过,就可能“永久不可用”。

2. ArkTS 插件层:确保 Proxy 真正被注册

2.1 检查 GamePlugin 的注入逻辑

在自定义插件中,我们通常会写类似逻辑(下述为示意):

class JsGameNative {
  private ui: UIContext;
  constructor(ui: UIContext) { this.ui = ui; }
  toast(message: string) {
    this.ui.getPromptAction().showToast({ message: String(message) });
  }
}

export class GamePlugin extends CordovaPlugin {
  private registered: boolean = false;
  private jsObj: JsGameNative | null = null;

  private tryRegisterProxy(): void {
    if (this.registered) return;
    const ctx = this.cordovaWebView?.getUIContext();
    const controller = this.cordovaWebView?.getWebviewController();
    if (ctx && controller) {
      if (!this.jsObj) {
        this.jsObj = new JsGameNative(ctx);
      }
      controller.registerJavaScriptProxy(this.jsObj as ESObject, 'gameNative', [], ['toast']);
      this.registered = true;
    }
  }

  initialize(ci: CordovaInterface, cw: CordovaWebView): void {
    this.tryRegisterProxy();
  }

  onPageEnd(): void {
    this.tryRegisterProxy();
  }
}

2.2 插件层常见问题

  • cordovaWebViewWebviewController 为 null
    • 说明初始化时机太早,可通过日志验证:
console.log('[GamePlugin] tryRegisterProxy ctx=', !!ctx, ' controller=', !!controller);
  • 只在 initialize 中注册
    • 部分设备/版本中,initialize 调用时 WebView 还未完全就绪,导致 Proxy 注册失败。
    • 建议同时在 onPageEnd 等生命周期中再试一次。
  • registered 标记用法错误
    • 若在失败路径也将 registered 设为 true,会导致后续不再重试。

建议:

  • 每次调用 tryRegisterProxy 前后均输出日志,明确是否真正执行了 registerJavaScriptProxy
  • 只在成功注册后才将 registered 置为 true

3. 多时机注入:MainPage + Plugin 双保险

除了在插件中注册 Proxy,你还可以在 MainPage 所在的页面层(如 Index.ets)利用生命周期做一次“兜底注册”。

3.1 Index.ets 层面的 Proxy 注册(思路)

思路是利用 MainPageCycle,在:

  • onControllerAttached:Web 控制器挂载时注册一次;
  • onSetCordovaWebAttribute(或等效回调):Cordova WebView 属性就绪时再注册一次。

通过这类“双保险”,即便插件侧偶尔时机不对,也能在页面层兜底,从而降低 window.gameNative 为 undefined 的概率。

时序图示例

Web(app.js) GamePlugin WebviewController MainPage Index.ets Web(app.js) GamePlugin WebviewController MainPage Index.ets build(MainPage props) onControllerAttached() registerJavaScriptProxy(gameNative) (第一次) initialize()/onPageEnd() registerJavaScriptProxy(gameNative) (第二次, 兜底) 调用 window.gameNative.toast()

4. Web 端:合理的降级与重试策略

即便原生注入逻辑完善,Web 端在调用时仍可能遇到 短暂的 undefined。合理的做法是:

  1. 提供统一封装函数,例如 showToast(msg)
  2. 先尝试 window.gameNative.toast,失败时降级到 cordova.exec
  3. 两者都不可用时,稍后重试。

4.1 推荐封装示意

function showToast(msg) {
  try {
    if (window.gameNative && typeof window.gameNative.toast === 'function') {
      window.gameNative.toast(msg);
    } else if (window.cordova && typeof window.cordova.exec === 'function') {
      window.cordova.exec(function(){}, function(){}, 'GamePlugin', 'toast', [{ message: msg }]);
    } else {
      setTimeout(() => showToast(msg), 200); // 等待注入
    }
  } catch (e) {
    console.error('[2048] toast error:', e);
  }
}

4.2 index.html 中等待注入(可选)

index.html 尾部,可以加一个简单的“注入探测脚本”,帮助你在调试阶段确认 Proxy 是否就绪:

<script>
  (function waitForNative() {
    if (window.gameNative) {
      console.log('[Init] gameNative ready');
    } else {
      console.log('[Init] gameNative not ready, retry...');
      setTimeout(waitForNative, 100);
    }
  })();
</script>

注意:

  • 正式发布时可以关闭频繁日志或移除此探测脚本,以免影响性能。

5. 常见问题场景与定位思路

场景 1:页面刚打开就调用 window.gameNative.toast,报 undefined

原因

  • Web 端在 <script> 一开始就调用了 window.gameNative.toast,而此时 ArkTS 侧 Proxy 尚未注册。

解决

  • 把调用逻辑放在 DOMContentLoadeddeviceready 之后。
  • 使用上文的 showToast 封装 + 延迟重试。

场景 2:某些设备上完全没有 window.gameNative

排查步骤

  1. 在插件 tryRegisterProxy 中输出 ctx/controller 是否为 null。
  2. 检查 GamePlugin 是否实际加入了 cordovaPlugs: Array<PluginEntry> 中。
  3. Index.etsMainPage 中增加额外一次 registerJavaScriptProxy 尝试。
  4. 在 Web 端使用 waitForNative 脚本观察是否有“ready”日志。

场景 3:偶发 undefined,重启 App 后又恢复正常

高概率原因:时序边界问题。

  • 某次启动时 ArkWeb 初始化稍慢,导致 Proxy 注册滞后。
  • 仅在单一生命周期中注册 Proxy,错过时机便不会再执行。

改进建议

  • initialize/onPageEnd/onControllerAttached/onSetCordovaWebAttribute 等多个关键节点尝试注册。
  • 通过日志观察实际哪一个阶段成功注册,然后在生产环境保留 2~3 个最稳妥的时机。

6. 端到端排查流程图

最后,用一张图总结 window.gameNative 为 undefined 时的排查路线:

window.gameNative 为 undefined

GamePlugin 是否加入 cordovaPlugs?

在 Index.ets 的 cordovaPlugs 中注册 GamePlugin

tryRegisterProxy 中 ctx/controller 是否为 null?

检查调用时机, 增加 onPageEnd/onControllerAttached 重试

日志中是否有 registerJavaScriptProxy 成功信息?

检查异常捕获/registered 标记逻辑

Web 端是否有降级与重试?

实现 showToast 封装 + 重试

结合具体设备日志持续优化时机

掌握这套排查方法后,你在 HarmonyOS + Cordova 项目中遇到任何 JS Proxy/window.xxx 注入问题,都可以沿着“插件注册 → 注入时机 → Web 调用”这条主线快速定位,而不是被零散现象牵着走。

欢迎加入开源鸿蒙PC社区:https://harmonypc.csdn.net/

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐