让 VK 小程序调用 HarmonyOS 原生能力:壳子 SDK 的实现思路

一句话结论

目标不是让 VK 小程序认识 HarmonyOS,也不是让它直接调用项目已有的 atomicsdk

目标是提供一个 VK Runtime / 壳子 SDK:小程序仍然按 VK 的方式调用 vk.scanCode()vk.getLocation();壳子在底层把调用转发给 ArkTS 和 HarmonyOS Kit。对小程序开发者而言,底层框架是透明的。

VK 小程序(HTML / CSS / JavaScript)
              │
              │ vk.scanCode()
              ▼
VK Runtime / 壳子 SDK
  - 注入 window.vk
  - 对齐 VK 的参数、返回值、错误码
  - Promise 与回调管理
  - 能力检测与权限状态
              │
              ▼
HarmonyOS WebSDK 底座
  - window.atomicsdk(内部实现,不对小程序公开)
  - ArkTS Bridge
              │
              ▼
HarmonyOS Kit
  - ScanKit、LocationKit、相册、文件等

当前的“出境服务”元服务只是验证壳子 SDK 的测试宿主,不是 SDK 的对外形态。

为什么需要壳子 SDK

如果让页面直接调用:

window.atomicsdk.invokeAsyncMethod({ appMethod: 'scanCode' })

页面会依赖鸿蒙项目的私有桥协议。以后切换小程序平台、修改桥接名、统一错误码或迁移宿主,所有页面都需要改。

而壳子 SDK 对页面提供稳定的 VK 接口:

const result = await vk.scanCode({
  enableAlbum: true
})

页面只依赖 VK API 契约;鸿蒙原生实现、权限逻辑和 WebView 通信细节都隐藏在壳子内部。

扫码 API 的完整调用链

vk.scanCode() 为例:

1. 用户点击 VK 小程序中的“扫码”按钮
2. 页面调用 vk.scanCode(options)
3. VK Runtime 创建唯一回调名,并调用内部 atomicsdk 桥
4. ArkTS 的 AtomicAppBridge 收到 appMethod = scanCode
5. ArkTS 调用 HarmonyOS ScanKit.startScanForResult()
6. ScanKit 打开系统扫码界面
7. 用户扫码或从相册选择二维码
8. ScanKit 返回原始结果
9. ArkTS 标准化结果并回调 JavaScript
10. VK Runtime 将结果转换为 VK API 的 Promise 成功或失败结果

1. VK 小程序页面:只调用 VK API

页面可以是普通 HTML、CSS、JavaScript:

<button id="scan">调用 vk.scanCode()</button>
<pre id="result"></pre>

<script>
  const resultEl = document.getElementById('result')

  document.getElementById('scan').addEventListener('click', async () => {
    try {
      const result = await vk.scanCode({ enableAlbum: true })
      resultEl.textContent = JSON.stringify(result, null, 2)
    } catch (error) {
      resultEl.textContent = JSON.stringify(error, null, 2)
    }
  })
</script>

这里的页面不需要知道 atomicsdk、ArkTS 或 ScanKit 的存在。

2. VK Runtime:把 VK API 映射到内部桥接

宿主加载小程序 WebView 前,注入下面的适配层。正式版应将它独立为可版本化的 vk-runtime.js,而不是写在页面业务代码中。

(() => {
  if (window.vk) return

  let sequence = 0

  function invokeNative(appMethod, data) {
    return new Promise((resolve, reject) => {
      const bridge = window.atomicsdk
      if (!bridge || typeof bridge.invokeAsyncMethod !== 'function') {
        reject({
          code: 'VK_API_UNAVAILABLE',
          message: 'Native VK API is unavailable'
        })
        return
      }

      const callbackName = `__vk_callback_${Date.now()}_${++sequence}`
      window[callbackName] = (result) => {
        delete window[callbackName]

        if (result && result.code === 0) {
          reject(result.data || result)
          return
        }

        resolve(
          result && Object.prototype.hasOwnProperty.call(result, 'data')
            ? result.data
            : result
        )
      }

      bridge.invokeAsyncMethod({
        appMethod,
        data: data || {},
        jsMethod: callbackName
      })
    })
  }

  window.vk = Object.freeze({
    scanCode(options) {
      return invokeNative('scanCode', options)
    }
  })
})()

关键点:

  • window.vk 是唯一的公开接口。
  • window.atomicsdk 只作为壳子内部实现细节。
  • 每次调用生成独立回调名,避免并发请求互相覆盖。
  • Runtime 负责把底层回包统一包装成 Promise。

3. ArkTS Bridge:接收 Web 调用

底座通过 HarmonyOS WebView 的 JavaScriptProxy 注入 atomicsdk。ArkTS 中的桥接对象登记 scanCode

this.addMethod('scanCode', (data, callback) => {
  if (!ScanCodeUtil.isValidOptions(data)) {
    return callback.onFail(JSBridgeErrorFactory.invalidParam())
  }
  return this.scanCode(data as ScanCodeOptions, callback)
})

实际扫码实现调用 HarmonyOS 的 ScanKit

async scanCode(data: ScanCodeOptions, callback: JSBridgeCallback) {
  if (this.scanCodeInProgress) {
    return callback.onFail(
      JSBridgeErrorFactory.invalid('scanCode already in progress')
    )
  }

  this.scanCodeInProgress = true
  try {
    const options = ScanCodeUtil.normalizeScanOptions(data)
    const result = await scanBarcode.startScanForResult(getContext(this), options)
    return callback.onSuccess(ScanCodeUtil.normalizeScanResult(result))
  } catch (error) {
    return callback.onFail(ScanCodeUtil.normalizeScanError(error))
  } finally {
    this.scanCodeInProgress = false
  }
}

这层才是真正的原生能力实现:小程序没有直接访问相机,也不需要直接调用 HarmonyOS API。

4. 宿主如何加载 Demo

第一版验证可以在元服务首页增加一个“VK Demo”入口:

首页 VK Demo 按钮
  → 打开本地 vk_demo.html
  → WebView 注入 window.vk
  → 页面调用 vk.scanCode()
  → 鸿蒙系统扫码

本地 HTML 的价值是将问题缩小到最小闭环:

  • WebView 是否加载成功;
  • window.vk 是否注入成功;
  • JavaScript 到 ArkTS 的异步回调是否可用;
  • 系统权限和 ScanKit 是否可用;
  • 扫码结果能否稳定回到页面。

接口扩展方式

后续每一个 VK P0 API 都遵循同一个映射模式:

VK APIRuntime 映射ArkTS/鸿蒙能力
vk.scanCodeatomicsdk.scanCodeScanKit
vk.getLocationatomicsdk.getLocationLocationKit
vk.chooseImageatomicsdk.chooseImagePhotoAccessHelper / 文件选择
vk.setClipboardDataatomicsdk.setClipboardDataPasteboard
vk.getNetworkTypeatomicsdk.getNetTypeNetworkKit

如果底座已有对应能力,只需完成协议适配;若不存在,则在 ArkTS 底座实现原生能力,再在 VK Runtime 暴露同名 API。

正式版必须补齐的能力

Demo 只验证“能调用”,正式 SDK 还需要:

  1. 严格契约对齐:以 VK 官方文档为准,不能只模仿方法名。
  2. 统一错误码:区分用户取消、权限拒绝、设备不支持、系统异常和参数错误。
  3. 能力检测:提供 vk.canIUse() 或等价机制。
  4. 可信来源控制:只对登记的 VK 小程序域名或包注入 window.vk
  5. 权限治理:按接口申请最小权限,向小程序返回明确的权限状态。
  6. 版本协商:Runtime 和原生底座分别有版本号,支持灰度和兼容判断。
  7. 可观测性:记录 API 名称、耗时、结果和错误码,不记录敏感扫码内容。

总结

这套方案的本质不是“在 WebView 里塞几个 JS 方法”,而是实现一个兼容 VK API 的运行时壳子:

VK 小程序不变
        ↓
VK Runtime 兼容层
        ↓
统一鸿蒙原生能力底座
        ↓
HarmonyOS 系统能力

这样,VK 小程序可以在鸿蒙元服务中运行,并获得扫码、定位、相册、剪贴板等原生能力;未来接入其他小程序平台时,复用同一套鸿蒙底座,只新增对应的平台 Runtime 即可。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐