《uni-app开发Harmony Next平台的App》第五篇:调用鸿蒙原生API——通过uts插件打开应用市场
用来声明 uni-app 层最终希望拿到的方法形态。productId?errMsg?: string参数和返回值必须明确,否则后续无论是类型推断还是调试都会变得很混乱。在 uni-app 的鸿蒙原生能力接入里,UTS 插件负责“正式接入”,scheme验证负责“快速排障”。两条路径并不冲突,反而应该配合使用。应用市场 scheme 是否可用插件是否真的注册成功。

从 uni-app 到鸿蒙原生:UTS 插件如何接入应用市场拉起能力
在 uni-app 编译到 HarmonyOS NEXT 平台时,定位、地图、WebView 等能力已经有大量内置封装。但像“打开华为应用市场详情页”“拉起系统应用”“接入系统级原生能力”这类场景,通常还是要走 UTS 插件。
UTS 的定位可以理解为:在 uni-app 的 JavaScript 层声明接口,在鸿蒙侧用 ArkTS 实现,再由 uni-app 编译链把两边连接起来。这样一来,页面层保持 uni-app 写法,原生能力仍然可以按鸿蒙方式接入。
本文以“打开华为应用市场详情页”为例,说明三件事:
- UTS 插件目录应该怎么组织
- ArkTS 实现应该怎么写
- 当
uni.openAppProduct没有成功注册时,如何先用scheme方式验证能力链路
环境说明
HBuilderX 版本:4.31+
目标平台:HarmonyOS NEXT
设备类型:手机真机优先
项目类型:uni-app vue3
UTS 插件只适用于 uni-app vue3 项目编译到鸿蒙平台。
项目结构
当前项目中的相关目录如下:
uni_modules/
└─ uni-app-market/
├─ package.json
├─ index.uts
└─ utssdk/
└─ ohos/
├─ interface.uts
└─ index.uts
pages/
└─ uts-demo/
└─ uts-demo.vue
这些文件分别承担下面的职责:
package.json:声明插件基础信息和 ArkTS 入口。index.uts:插件根目录入口。utssdk/ohos/interface.uts:定义对外暴露的方法签名。utssdk/ohos/index.uts:保留鸿蒙实现文件,便于后续恢复标准目录结构。pages/uts-demo/uts-demo.vue:演示页面,用来验证应用市场拉起链路。
第一步:创建 UTS 插件
插件放在 uni_modules 目录中,名称使用 uni-app-market。
package.json
{
"id": "uni-app-market",
"displayName": "打开应用市场",
"version": "1.0.0",
"description": "通过 uts 插件调用鸿蒙原生 API 打开应用市场详情页",
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"HarmonyOS": {
"app-harmony": "y"
}
}
},
"arkts": {
"supported": true,
"entry": "index.uts"
}
}
}
这里有两个关键点:
- 需要显式声明
HarmonyOS.app-harmony。 arkts.entry指向index.uts,让鸿蒙编译链从插件根目录入口开始识别。
第二步:定义接口签名
utssdk/ohos/interface.uts 用来声明 uni-app 层最终希望拿到的方法形态。
export interface OpenAppProductOptions {
packageName: string
productId?: string
}
export interface OpenAppProductResult {
success: boolean
errMsg?: string
}
export function openAppProduct(options: OpenAppProductOptions): Promise<OpenAppProductResult>
export interface OpenSystemAppOptions {
bundleName: string
abilityName?: string
parameters?: Record<string, string>
}
export interface OpenSystemAppResult {
success: boolean
errMsg?: string
}
export function openSystemApp(options: OpenSystemAppOptions): Promise<OpenSystemAppResult>
这一步的作用是把页面层想调用的接口描述清楚,例如:
uni.openAppProduct(...)uni.openSystemApp(...)
参数和返回值必须明确,否则后续无论是类型推断还是调试都会变得很混乱。
第三步:编写 ArkTS 实现
当前项目把主实现写在插件根目录的 index.uts 中,核心思路是通过 WantAgent 拉起华为应用市场。
import { wantAgent, WantAgent } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
export async function openAppProduct(options: {
packageName: string;
productId?: string;
}): Promise<{ success: boolean; errMsg?: string }> {
try {
const targetId = options.productId || options.packageName;
const want: Want = {
deviceId: '',
bundleName: 'com.huawei.appgallery',
abilityName: 'com.huawei.appgallery.MainAbility',
uri: `appgallery://productDetail?id=${targetId}`,
parameters: {
'productId': targetId
}
};
const wantAgentInfo: wantAgent.WantAgentInfo = {
wants: [want],
operationType: wantAgent.OperationType.START_ABILITY,
requestCode: 0,
wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
};
const agent: WantAgent = await wantAgent.getWantAgent(wantAgentInfo);
await wantAgent.trigger(agent, {
code: 0
});
return { success: true };
} catch (error) {
const err = error as BusinessError;
return { success: false, errMsg: err.message };
}
}
实现要点
- 不依赖
context。 - 使用
@kit.AbilityKit和@kit.BasicServicesKit。 - 优先使用
appgallery://productDetail?id=...这种 scheme。 - 统一返回
{ success, errMsg },便于页面层直接消费。
第四步:在 uni-app 页面里调用
理想情况下,插件编译成功后,页面可以直接调用:
const result = await uni.openAppProduct({
packageName: 'com.example.app',
productId: '123456'
})
或者:
const result = await uni.openSystemApp({
bundleName: 'com.huawei.settings',
abilityName: 'com.huawei.settings.MainAbility'
})
但是在实际项目里,页面能不能直接拿到这两个方法,不取决于源码有没有写,而取决于鸿蒙生成产物里是否已经完成扩展 API 注册。
第五步:判断插件是否真的注册成功
最直接的检查方式不是看源码,而是看生成后的鸿蒙文件:
unpackage/dist/dev/app-harmony/entry/src/main/ets/uni_modules/index.generated.ets
如果这个文件里类似下面这样:
function initUniExtApi() {
}
说明插件还没有真正注册进 uni 扩展 API 链。
这时就会出现典型报错:
uni.openAppProduct is not a function
也就是说:
- 源码目录存在
- ArkTS 文件存在
package.json也写了
并不代表最终就一定会生成 uni.openAppProduct。
第六步:先用 scheme 验证能力链路
当 uni.openAppProduct 还没有注册成功时,最稳妥的做法不是继续盲改插件结构,而是先验证设备本身能不能接住应用市场的 scheme。
当前项目的 pages/uts-demo/uts-demo.vue 就采用了这个方式:
openAppMarketByScheme() {
return new Promise((resolve) => {
if (typeof plus === 'undefined' || !plus.runtime || !plus.runtime.openURL) {
resolve({
success: false,
errMsg: '当前运行环境不支持 plus.runtime.openURL'
});
return;
}
const targetId = this.productId || this.packageName;
const schemeUrl = `appgallery://productDetail?id=${encodeURIComponent(targetId)}`;
plus.runtime.openURL(
schemeUrl,
() => {
resolve({ success: true });
},
(err) => {
const errMsg = err && err.message ? err.message : JSON.stringify(err || {});
resolve({
success: false,
errMsg: `scheme 拉起失败: ${errMsg}`
});
}
);
});
}
这样做的目的
先把问题拆成两个层级:
- UTS 插件有没有注册成功
appgallery://productDetail?id=...本身能不能被系统识别
如果第一步还没通,就没必要一开始就把所有故障都归咎于原生拉起代码。
第七步:推荐的排查顺序
先验证 scheme
步骤如下:
- 打开
UTS 插件演示页面 - 点击“打开应用市场详情页”
- 观察日志是否出现:
尝试拉起 scheme: appgallery://productDetail?id=...
如果成功拉起:
- 说明应用市场 scheme 本身是可用的
- 问题集中在 UTS 注册链
如果拉起失败:
- 说明设备不接受该 scheme
- 或者传入的
id不是应用市场支持的真实产品 ID
再处理 UTS 注册问题
当 scheme 已经可用后,再集中检查:
package.json字段层级是否正确arkts.entry是否被当前 HBuilderX 识别index.generated.ets是否开始生成注册代码uni.openAppProduct是否真的出现在运行时
常见问题
1. 为什么源码里已经有 index.uts,页面里还是提示 uni.openAppProduct is not a function?
因为源码存在不等于注册成功。真正的判断标准是生成产物里有没有把这个插件注入到 initUniExtApi()。
2. 为什么页面里要同时保留 UTS 方案和 plus.runtime.openURL 方案?
因为两者解决的问题不同:
- UTS 解决“uni-app 如何调用鸿蒙原生能力”
plus.runtime.openURL解决“当前设备能否接住应用市场 scheme”
先验证后者,可以快速判断原生拉起链路是否本身可用。
3. 为什么不建议直接改 unpackage/dist 下的 ArkTS 文件?
因为这是生成目录:
- 重新编译会被覆盖
- 临时注入代码容易触发 ArkTS 语法和类型约束错误
- 不适合作为长期方案
4. 为什么 JS 包装层不能直接把方法再挂回 uni.openAppProduct?
因为如果包装层内部再去调用 uni.openAppProduct,就很容易造成递归调用,日志里会不断重复打印 openAppProduct 被调用。
5. 为什么真机比模拟器更重要?
因为应用市场、系统应用、scheme 拉起这类能力经常依赖设备环境。模拟器很多时候没有完整系统应用或不具备完整拉起能力。
最佳实践
-
UTS 接入和能力验证分开做
- UTS 负责正式原生接入
- 页面层先用 scheme 验证链路
-
先看生成产物,再看源码
- 不要只盯着
uni_modules目录 - 要看
index.generated.ets里是否真的注册成功
- 不要只盯着
-
不要直接长期修改
unpackage/dist- 可以临时定位问题
- 不适合作为正式实现
-
优先返回统一结果结构
- 例如
{ success, errMsg } - 页面层处理更简单
- 例如
-
优先真机测试
- 尤其是应用市场、系统设置、浏览器等系统级跳转能力
总结
在 uni-app 的鸿蒙原生能力接入里,UTS 插件负责“正式接入”,scheme 验证负责“快速排障”。两条路径并不冲突,反而应该配合使用。
处理这类问题时,最重要的不是一开始就强行把所有逻辑都塞进 UTS,而是先搞清楚:
- 应用市场 scheme 是否可用
- 插件是否真的注册成功
- 页面拿到的到底是运行时方法,还是一个并未生效的理论接口
把这三件事分开,排查效率会高很多,教程也会更接近项目里的真实开发流程。
更多推荐


所有评论(0)