让 VK 小程序调用 HarmonyOS 原生能力:壳子 SDK 的实现思路
一句话结论
目标不是让 VK 小程序认识 HarmonyOS,也不是让它直接调用项目已有的 atomicsdk。
目标是提供一个 VK Runtime / 壳子 SDK :小程序仍然按 VK 的方式调用 vk.scanCode()、vk.getLocation();壳子在底层把调用转发给 ArkTS 和 HarmonyOS Kit。对小程序开发者而言,底层框架是透明的。
text
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
如果让页面直接调用:
js
window.atomicsdk.invokeAsyncMethod({ appMethod: 'scanCode' })
页面会依赖鸿蒙项目的私有桥协议。以后切换小程序平台、修改桥接名、统一错误码或迁移宿主,所有页面都需要改。
而壳子 SDK 对页面提供稳定的 VK 接口:
js
const result = await vk.scanCode({
enableAlbum: true
})
页面只依赖 VK API 契约;鸿蒙原生实现、权限逻辑和 WebView 通信细节都隐藏在壳子内部。
扫码 API 的完整调用链
以 vk.scanCode() 为例:
text
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:
html
<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,而不是写在页面业务代码中。
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:
ts
this.addMethod('scanCode', (data, callback) => {
if (!ScanCodeUtil.isValidOptions(data)) {
return callback.onFail(JSBridgeErrorFactory.invalidParam())
}
return this.scanCode(data as ScanCodeOptions, callback)
})
实际扫码实现调用 HarmonyOS 的 ScanKit:
ts
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"入口:
text
首页 VK Demo 按钮
→ 打开本地 vk_demo.html
→ WebView 注入 window.vk
→ 页面调用 vk.scanCode()
→ 鸿蒙系统扫码
本地 HTML 的价值是将问题缩小到最小闭环:
- WebView 是否加载成功;
window.vk是否注入成功;- JavaScript 到 ArkTS 的异步回调是否可用;
- 系统权限和 ScanKit 是否可用;
- 扫码结果能否稳定回到页面。
接口扩展方式
后续每一个 VK P0 API 都遵循同一个映射模式:
| VK API | Runtime 映射 | ArkTS/鸿蒙能力 |
|---|---|---|
vk.scanCode |
atomicsdk.scanCode |
ScanKit |
vk.getLocation |
atomicsdk.getLocation |
LocationKit |
vk.chooseImage |
atomicsdk.chooseImage |
PhotoAccessHelper / 文件选择 |
vk.setClipboardData |
atomicsdk.setClipboardData |
Pasteboard |
vk.getNetworkType |
atomicsdk.getNetType |
NetworkKit |
如果底座已有对应能力,只需完成协议适配;若不存在,则在 ArkTS 底座实现原生能力,再在 VK Runtime 暴露同名 API。
正式版必须补齐的能力
Demo 只验证"能调用",正式 SDK 还需要:
- 严格契约对齐:以 VK 官方文档为准,不能只模仿方法名。
- 统一错误码:区分用户取消、权限拒绝、设备不支持、系统异常和参数错误。
- 能力检测 :提供
vk.canIUse()或等价机制。 - 可信来源控制 :只对登记的 VK 小程序域名或包注入
window.vk。 - 权限治理:按接口申请最小权限,向小程序返回明确的权限状态。
- 版本协商:Runtime 和原生底座分别有版本号,支持灰度和兼容判断。
- 可观测性:记录 API 名称、耗时、结果和错误码,不记录敏感扫码内容。
总结
这套方案的本质不是"在 WebView 里塞几个 JS 方法",而是实现一个兼容 VK API 的运行时壳子:
text
VK 小程序不变
↓
VK Runtime 兼容层
↓
统一鸿蒙原生能力底座
↓
HarmonyOS 系统能力
这样,VK 小程序可以在鸿蒙元服务中运行,并获得扫码、定位、相册、剪贴板等原生能力;未来接入其他小程序平台时,复用同一套鸿蒙底座,只新增对应的平台 Runtime 即可。