HarmonyOS 7 · ArkWeb 混合应用开发手记 03

上一篇已经把 H5 和 ArkTS
的双向调用跑通了。真正进业务以后,问题很快就从"能不能调用"变成"调用多了以后怎么不乱"。分享、登录、支付、定位、设备信息一起进来,如果每个方法都有自己的参数和回调,Bridge
很快会变成一团线。

这一篇不继续加能力,先把通信协议定下来。核心只有四个词:method、requestId、params、统一响应。

1. 为什么需要 requestId

H5
连续发两个异步请求时,返回顺序不一定和发出顺序一样。最稳的做法,是每次请求都带一个唯一
ID:

interface BridgeRequest {
  requestId: string
  method: string
  params: string
}

interface BridgeResponse {
  requestId: string
  code: number
  message: string
  data: string
}

requestId 就像快递单号。结果回来以后,不需要猜它属于哪个请求。

【配图 01:BridgeRequest / BridgeResponse 协议代码,红圈标 requestId】

2. H5 侧只保留一个入口

不要继续增加 getToken()、openShare()、getLocation()
这种全局入口。可以把调用收成:

function callNative(method, params = {}) {
  const requestId = `${Date.now()}_${Math.random()}`
  return new Promise((resolve, reject) => {
    pending.set(requestId, { resolve, reject })
    NativeBridge.invoke(JSON.stringify({
      requestId,
      method,
      params
    }))
  })
}

业务侧就很舒服:

const result = await callNative('getAppInfo')

3. ArkTS 做统一分发

原生只暴露一个 invoke:

invoke(raw: string): void {
  try {
    const request: BridgeRequest = JSON.parse(raw)
    this.dispatch(request)
  } catch (e) {
    console.error('[Bridge] invalid request')
  }
}

再按 method 分发:

private dispatch(req: BridgeRequest): void {
  switch (req.method) {
    case 'getAppInfo':
      this.getAppInfo(req)
      break
    case 'openShare':
      this.openShare(req)
      break
    default:
      this.reply(req.requestId, 404, 'method not found', '')
  }
}

这里宁可多写一个 switch,也不要动态执行任意方法名。Bridge
是能力边界,不是万能反射器。

4. 返回结构必须统一

成功和失败都走同一个结构:

private reply(id: string, code: number,
  message: string, data: string): void {
  const result: BridgeResponse = {
    requestId: id,
    code,
    message,
    data
  }
  this.sendToH5(result)
}

H5 收到后只处理一次:

window.onNativeMessage = function (result) {
  const task = pending.get(result.requestId)
  if (!task) return

  pending.delete(result.requestId)

  if (result.code === 0) {
    task.resolve(result.data)
  } else {
    task.reject(new Error(result.message))
  }
}

请添加图片描述

5. Promise 不是重点,超时才是

最容易漏的是:原生如果永远不回,Promise 就永远 pending。

const timer = setTimeout(() => {
  pending.delete(requestId)
  reject(new Error(`Bridge timeout: ${method}`))
}, 10000)

收到结果时记得 clearTimeout(timer)。10 秒不是标准值,要按业务调整。

6. 错误码不要随手写

至少分清:成功、参数错误、方法不存在、业务失败、超时。

enum BridgeCode {
  SUCCESS = 0,
  INVALID_PARAMS = 400,
  METHOD_NOT_FOUND = 404,
  BUSINESS_ERROR = 500
}

别今天返回 -1,明天返回 false,后天又返回字符串
"error"。这种协议最难维护。

7. 给协议加版本

interface BridgeRequest {
  version: string
  requestId: string
  method: string
  params: string
}

H5 和 App
不一定同时发版。版本字段不是为了显得专业,而是给以后兼容留出口。

8. 日志也按 requestId 串起来

[Bridge][REQ][req_1001] getAppInfo
[Bridge][RES][req_1001] code=0

线上排查时,只搜一个 ID 就能看到完整链路。

9. 不要把敏感能力直接暴露

支付、账号、文件、定位都应该有白名单和参数校验。Bridge 收到 method
不等于一定执行。

private allowedMethods: Set<string> =
  new Set(['getAppInfo', 'openShare'])

来源、页面、登录态和业务权限需要按项目继续校验。

10. 这一篇真正得到什么

到这里,JSBridge 从"几个能调用的方法"变成了一条协议:请求有
ID,响应有固定结构,异步有超时,能力有白名单,日志可以串联。

下一篇我们处理另一个很真实的问题:H5
连续跳了三层以后,用户按返回,到底应该退网页还是退 ArkUI 页面?

Logo

加入「COC·上海城市开发者社区」,成就更好的自己!

更多推荐