uni-app UTS 插件开发说明
这篇文章解决什么问题
很多人第一次看 UTS 插件 文档时,会把几个概念混在一起:
UTS是一种语言,不是插件本身。UTS 插件是用 UTS 写出的uni_modules插件。- uni-app 中调用 UTS 插件,前端页面仍然可以用普通 JavaScript。
- Android / iOS / HarmonyOS 的实现代码可以分目录写。
- API 插件和组件插件的使用方式完全不同。
本文只讲 uni-app 里的 UTS 插件开发和使用,不展开 uni-app x。
先说结论
UTS 插件可以理解成:
用接近 TypeScript 的写法封装原生能力,再以
uni_modules插件形式给 uni-app 页面调用。
它最适合解决这类问题:
- 调 Android / iOS / HarmonyOS 系统 API
- 封装第三方原生 SDK
- 做一个可复用的 App 原生能力插件
- 希望同一个插件同时支持 App、Web、小程序等多个端
- 希望前端页面直接
import调用,而不是使用uni.requireNativePlugin
不适合的情况:
- 纯前端就能完成的功能,不需要 UTS
- 对原生系统 API 完全不了解,不适合直接写 UTS
- 只想通过 CLI 创建和使用插件,目前官方文档仍强调需要通过 HBuilderX 创建和使用
- 大量现成 Java / Kotlin / Swift 代码不想改写,可以考虑原生混编或封装成 AAR / Framework
UTS 和 UTS 插件不是一回事
UTS 全称是 uni type script。它是一种统一、强类型、脚本语言。
在不同平台上,它会进入不同的编译方向:
| 平台 | UTS 编译方向 | 典型用途 |
|---|---|---|
| Web | JavaScript | 做 Web 兜底或模拟实现 |
| Android | Kotlin | 调 Android 系统 API、三方 SDK |
| iOS | Swift | 调 iOS 系统 API、三方 SDK |
| HarmonyOS | ArkTS | 调 HarmonyOS 能力 |
UTS 插件 是用 UTS 写成的插件。它被放在 uni_modules 目录里,供 uni-app 页面调用。
在 uni-app 中,调用关系是:
text
uni-app 页面中的 JavaScript
-> import UTS 插件
-> UTS 插件按当前平台选择实现
-> App 端进入原生能力所以不要把 UTS 插件理解成“页面里的 JS 工具函数”。在 App 端,它最终是真正的原生实现。
横向对比
和普通 JavaScript 工具函数对比
| 对比项 | 普通 JS 函数 | UTS 插件 |
|---|---|---|
| 运行位置 | JS 引擎 | App 端可进入原生层 |
| 能力范围 | 浏览器 / uni-app 已暴露能力 | 可调用系统 API 和原生 SDK |
| 性能 | 受 JS 环境限制 | App 端更接近原生能力 |
| 开发成本 | 低 | 需要理解平台 API |
| 适合场景 | 格式处理、业务逻辑、轻量能力 | 原生能力、三方 SDK、平台差异能力 |
如果功能用 uni 内置 API 或普通 JS 能完成,不要为了“更底层”而写 UTS。
和 Native.js 对比
| 对比项 | Native.js | UTS 插件 |
|---|---|---|
| 调用方式 | JS 通过反射调用系统 API | UTS 编译到原生方向 |
| 性能和稳定性 | 受反射和运行环境影响 | App 端是真正原生执行 |
| 类型约束 | 弱 | 更强 |
| 维护成本 | 容易写出难排查的问题 | 结构更清晰 |
| 推荐程度 | 适合少量临时能力 | 更适合正式插件化封装 |
Native.js 像是在 JS 里临时伸手调用原生,UTS 插件更像是把原生能力整理成一个正式模块。
和旧版 App 原生语言插件对比
| 对比项 | App 原生语言插件 | UTS 插件 |
|---|---|---|
| 开发语言 | Java / Objective-C 等 | UTS |
| 开发工具 | Android Studio / Xcode | HBuilderX 为主 |
| 前端调用 | uni.requireNativePlugin() | 普通 import |
| 支持项目 | uni-app | uni-app 和 uni-app x |
| 插件市场趋势 | 官方已停止新增受理 | 官方推荐方向 |
| 多端扩展 | 主要 Android / iOS | 可扩展 App、Web、小程序、HarmonyOS |
如果是新插件开发,优先考虑 UTS 插件。
API 插件和组件插件对比
| 类型 | 使用位置 | 适合做什么 | 示例 |
|---|---|---|---|
| API 插件 | script 中调用 | 权限、设备信息、支付、定位、蓝牙、系统能力 | getBatteryInfo() |
| 组件插件 | template 中使用 | 嵌入页面的原生视图 | Lottie 动画组件、地图组件 |
一句话判断:
- 能通过函数返回结果的,优先做 API 插件。
- 必须嵌进页面布局里的,做组件插件。
推荐目录结构
官方推荐把插件放在 uni_modules 下。
一个完整插件可以长这样:
text
uni_modules/
uts-battery-lite/
package.json
static/
utssdk/
interface.uts
unierror.uts
index.uts
app-android/
index.uts
config.json
AndroidManifest.xml
assets/
libs/
res/
app-ios/
index.uts
config.json
info.plist
app-harmony/
index.uts
web/
index.uts
mp-weixin/
index.js核心文件只需要先记住这几个:
| 文件 | 作用 |
|---|---|
package.json | 插件清单,描述插件 ID、名称、版本和依赖 |
utssdk/interface.uts | 定义对外暴露的 API 类型、参数、返回值、错误类型 |
utssdk/unierror.uts | 定义标准错误对象,可选 |
utssdk/index.uts | 跨平台入口,可选 |
utssdk/app-android/index.uts | Android 平台实现 |
utssdk/app-ios/index.uts | iOS 平台实现 |
utssdk/web/index.uts | Web 平台实现或兜底 |
入口选择规则:
| 情况 | 实际使用哪个入口 |
|---|---|
当前平台有分平台 index.uts | 优先使用分平台实现 |
当前平台没有分平台 index.uts | 回退到 utssdk/index.uts |
| 只做 Android 插件 | 可以只写 app-android/index.uts |
| 多端插件 | 建议写 interface.uts,各端分别实现 |
Demo:封装一个获取电量的 API 插件
下面用 uts-battery-lite 做一个完整示例。目标是:
- Android 调系统电量 API
- Web 给出不支持提示
- uni-app 页面用普通 JS 调用
- 返回结构统一,方便页面处理
第 1 步:创建插件
在 HBuilderX 中:
text
uni_modules -> 右键 -> 新建插件 -> uts插件插件名:
text
uts-battery-lite第 2 步:写 package.json
json
{
"id": "uts-battery-lite",
"displayName": "轻量电量读取插件",
"version": "0.1.0",
"description": "使用 UTS 封装 Android 电量读取能力,并提供 Web 兜底",
"uni_modules": {}
}插件 ID 很重要,前端导入路径会用到它:
js
import { getBatteryInfo } from '@/uni_modules/uts-battery-lite'第 3 步:写 utssdk/interface.uts
interface.uts 的作用是先把“这个插件对外提供什么能力”说清楚。它不负责具体实现。
ts
export type BatteryInfo = {
level: number
isCharging: boolean
}
export type BatteryInfoFail = {
errCode: number
errMsg: string
}
export type GetBatteryInfoOptions = {
success?: (res: BatteryInfo) => void
fail?: (err: BatteryInfoFail) => void
complete?: (res: any) => void
}
export type GetBatteryInfo = (options: GetBatteryInfoOptions) => void这里推荐使用回调风格,因为它和 uni-app 常见 API 风格一致:
js
getBatteryInfo({
success(res) {},
fail(err) {},
complete(res) {}
})第 4 步:写 Android 实现
文件:
text
uni_modules/uts-battery-lite/utssdk/app-android/index.uts代码:
ts
import Intent from 'android.content.Intent'
import IntentFilter from 'android.content.IntentFilter'
import BatteryManager from 'android.os.BatteryManager'
import { UTSAndroid } from 'io.dcloud.uts'
import { GetBatteryInfo, BatteryInfo, BatteryInfoFail } from '../interface.uts'
export const getBatteryInfo: GetBatteryInfo = function (options) {
const context = UTSAndroid.getAppContext()
if (context == null) {
const err: BatteryInfoFail = {
errCode: 1001,
errMsg: 'getBatteryInfo:fail app context is null'
}
options.fail?.(err)
options.complete?.(err)
return
}
const batteryStatus = context.registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED))
if (batteryStatus == null) {
const err: BatteryInfoFail = {
errCode: 1002,
errMsg: 'getBatteryInfo:fail battery status is null'
}
options.fail?.(err)
options.complete?.(err)
return
}
const level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
const scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
const status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1)
const res: BatteryInfo = {
level: scale > 0 ? Math.round(level * 100 / scale) : -1,
isCharging: status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL
}
options.success?.(res)
options.complete?.(res)
}这段代码做了三件事:
- 通过
UTSAndroid.getAppContext()获取 Android 上下文。 - 通过系统电池广播读取电量和充电状态。
- 用
success / fail / complete把结果返回给 uni-app 页面。
第 5 步:写 Web 兜底实现
文件:
text
uni_modules/uts-battery-lite/utssdk/web/index.uts代码:
ts
import { GetBatteryInfo, BatteryInfoFail } from '../interface.uts'
export const getBatteryInfo: GetBatteryInfo = function (options) {
const err: BatteryInfoFail = {
errCode: 2001,
errMsg: 'getBatteryInfo:fail current platform is not supported'
}
options.fail?.(err)
options.complete?.(err)
}为什么不强行在 Web 里也读电量:
- 浏览器电量 API 支持度不稳定。
- uni-app 页面更需要稳定可预期的返回。
- 明确返回“不支持”比返回假数据更好排查。
第 6 步:在 uni-app 页面中调用
页面文件示例:
vue
<template>
<view class="page">
<view class="title">设备电量</view>
<view class="value">{{ batteryText }}</view>
<button type="primary" @click="readBattery">读取电量</button>
</view>
</template>
<script>
import { getBatteryInfo } from '@/uni_modules/uts-battery-lite'
export default {
data() {
return {
batteryText: '未读取'
}
},
methods: {
readBattery() {
getBatteryInfo({
success: (res) => {
this.batteryText = res.level + '%'
uni.showToast({
title: res.isCharging ? '正在充电' : '未充电',
icon: 'none'
})
},
fail: (err) => {
this.batteryText = err.errMsg
}
})
}
}
}
</script>
<style>
.page {
padding: 32rpx;
}
.title {
font-size: 32rpx;
font-weight: 600;
margin-bottom: 24rpx;
}
.value {
font-size: 48rpx;
margin-bottom: 32rpx;
}
</style>前端调用时有一个硬规则:
js
// 正确:只导入插件根目录
import { getBatteryInfo } from '@/uni_modules/uts-battery-lite'
// 错误:不要直接导入内部 uts 文件
import { getBatteryInfo } from '@/uni_modules/uts-battery-lite/utssdk/app-android/index.uts'原因是:前端只应该面向插件入口,平台选择和编译交给 UTS 插件机制处理。
API 插件的设计建议
1. 优先设计成 uni-app 风格
建议:
js
getBatteryInfo({
success(res) {},
fail(err) {},
complete(res) {}
})不建议:
js
getBatteryInfo(true, false, function () {})原因很简单:参数越多,后续越难扩展。对象参数更适合跨端和版本升级。
2. 返回结构要稳定
推荐:
ts
export type BatteryInfo = {
level: number
isCharging: boolean
}不要今天返回字符串:
js
'80%'明天又返回对象:
js
{ level: 80 }插件一旦被页面使用,返回结构就是契约,频繁变化会让调用方很难维护。
3. 错误也要有结构
推荐:
ts
export type BatteryInfoFail = {
errCode: number
errMsg: string
}比只返回一个字符串更好:
js
'context is null'因为页面可以根据 errCode 做分支处理,也方便日志排查。
组件插件怎么理解
API 插件是在 script 里调用:
js
import { getBatteryInfo } from '@/uni_modules/uts-battery-lite'
getBatteryInfo({
success(res) {}
})组件插件是在 template 里使用:
vue
<template>
<uts-lottie-view class="animation" />
</template>组件插件更像“原生视图控件”,适合这些场景:
- Lottie 动画
- 原生地图
- 相机预览
- 视频渲染
- 自定义原生输入控件
不要把弹窗、全屏页面这类能力硬做成组件插件。它们通常更适合 API 插件。
Android 原生配置怎么放
Android 平台目录是:
text
utssdk/app-android/常见文件作用:
| 文件或目录 | 放什么 |
|---|---|
index.uts | Android 平台具体实现 |
config.json | Gradle 依赖、仓库、最低系统版本、CPU 架构等 |
AndroidManifest.xml | 插件内置的权限、组件声明 |
assets/ | 插件内置 assets 资源 |
res/ | 插件内置 res 资源 |
libs/ | 插件内置 jar / aar / so |
推荐原则:
- 插件内置资源放插件目录。
- 使用者自己配置的授权文件、证书、App 专属资源,不要塞进插件目录,应写在插件文档里让使用者放到项目指定位置。
- 三方 SDK 如果支持 Maven 仓库,优先写
config.json的依赖,不优先丢进libs。 - 多个插件引用同一个 SDK 时,直接复制 jar / aar 更容易冲突。
示例 config.json:
json
{
"abis": ["armeabi-v7a", "arm64-v8a"],
"dependencies": [
"androidx.core:core-ktx:1.6.0",
{
"id": "com.example:sdk",
"source": "implementation 'com.example:sdk:1.0.0'"
}
],
"minSdkVersion": 21,
"project": {
"repositories": [
"maven { url 'https://example.com/repository/' }"
]
}
}这类配置在云打包中会被合并到原生工程。离线 SDK 场景要单独按离线 SDK 规则处理。
Android 引用第三方 SDK 怎么做
Android UTS 插件接入第三方 SDK 时,核心不是“在页面里引用 SDK”,而是在插件内部完成三件事:
- 在
config.json里声明 SDK 依赖。 - 在
app-android/index.uts里导入 SDK 的 Java / Kotlin 类。 - 把 SDK 的能力包装成 uni-app 页面能稳定调用的 API。
整体关系是:
text
uni-app 页面
-> import UTS 插件
-> UTS 插件调用第三方 Android SDK
-> UTS 插件把结果转换成普通对象返回给页面页面不应该直接接触第三方 Android SDK。页面只关心插件提供的 API。
三种接入方式对比
| SDK 形态 | 怎么接入 | 优点 | 缺点 | 推荐程度 |
|---|---|---|---|---|
| Maven 依赖 | 写到 config.json.dependencies | 最清晰,版本可控,不容易丢文件 | 依赖仓库必须可访问 | 推荐 |
| 本地 AAR / JAR | 放到 app-android/libs | 适合厂商只给离线包的 SDK | 多插件重复引用时容易冲突 | 可用 |
| SO + Java 包装层 | SO 放 libs,Java/Kotlin 包装成 AAR | 适合底层算法、硬件厂商 SDK | UTS 本地调试直接使用 SO 有限制,维护成本高 | 谨慎 |
优先级建议:
text
Maven 依赖 > AAR / JAR > SO + 包装层config.json 要写什么
第三方 SDK 常见配置项:
json
{
"dependencies": [
{
"id": "com.github.vendor:sdk",
"source": "implementation 'com.github.vendor:sdk:1.0.0'"
}
],
"minSdkVersion": 21,
"project": {
"repositories": [
"maven { url 'https://jitpack.io' }"
]
}
}含义:
| 字段 | 作用 |
|---|---|
dependencies | 当前插件需要的 Android SDK 依赖 |
source | 最终会写进 Gradle 的依赖语句 |
minSdkVersion | SDK 要求的最低 Android 版本 |
project.repositories | SDK 所在的 Maven 仓库 |
如果 SDK 已经在默认仓库中,不一定要写 project.repositories。如果 SDK 在厂商自建仓库或 JitPack 之类的仓库中,就要按 SDK 官方 Gradle 接入文档补上。
如果 SDK 还要求权限,就写 AndroidManifest.xml:
xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.CAMERA" />
</manifest>如果 SDK 要求 appKey、渠道号、授权文件,不建议写死在 UTS 代码里。更好的方式是:
- appKey 通过插件 API 参数传入,或让使用者在项目配置中填写。
- 授权文件按 SDK 官方要求放到宿主 App 指定目录。
- 初始化方法单独封装成
initSdk(),不要在每个业务 API 中重复初始化。
案例:用 ZXing SDK 生成二维码
这个案例演示如何在 Android UTS 插件中引用第三方 Maven SDK。
目标:
- UTS 插件接入
com.google.zxing:core - Android 端生成二维码图片
- 返回
base64给 uni-app 页面展示 - Web 端返回明确的不支持提示
插件名:
text
uts-qrcode-zxing目录:
text
uni_modules/
uts-qrcode-zxing/
package.json
utssdk/
interface.uts
app-android/
index.uts
config.json
web/
index.uts第 1 步:写 package.json
json
{
"id": "uts-qrcode-zxing",
"displayName": "ZXing 二维码插件",
"version": "0.1.0",
"description": "使用 Android 第三方 SDK ZXing 生成二维码图片",
"uni_modules": {}
}第 2 步:写 Android config.json
文件:
text
uni_modules/uts-qrcode-zxing/utssdk/app-android/config.json代码:
json
{
"dependencies": [
{
"id": "com.google.zxing:core",
"source": "implementation 'com.google.zxing:core:3.5.3'"
}
],
"minSdkVersion": 21
}这里的 3.5.3 是示例版本,真实项目可以按 ZXing 或 SDK 官方文档更新。
这个插件不需要摄像头权限,因为它只是生成二维码,不扫码。
如果是扫码 SDK,通常还需要:
xml
<uses-permission android:name="android.permission.CAMERA" />第 3 步:写 interface.uts
文件:
text
uni_modules/uts-qrcode-zxing/utssdk/interface.uts代码:
ts
export type QrCodeResult = {
base64: string
width: number
height: number
}
export type QrCodeFail = {
errCode: number
errMsg: string
}
export type CreateQrCodeOptions = {
text: string
size?: number
success?: (res: QrCodeResult) => void
fail?: (err: QrCodeFail) => void
complete?: (res: any) => void
}
export type CreateQrCode = (options: CreateQrCodeOptions) => void这里把输入和输出固定下来:
- 输入:
text和可选size - 输出:图片
base64、宽度、高度 - 错误:统一返回
errCode和errMsg
第 4 步:写 Android 实现
文件:
text
uni_modules/uts-qrcode-zxing/utssdk/app-android/index.uts代码:
ts
import BarcodeFormat from 'com.google.zxing.BarcodeFormat'
import MultiFormatWriter from 'com.google.zxing.MultiFormatWriter'
import Bitmap from 'android.graphics.Bitmap'
import Color from 'android.graphics.Color'
import Base64 from 'android.util.Base64'
import ByteArrayOutputStream from 'java.io.ByteArrayOutputStream'
import { CreateQrCode, QrCodeResult, QrCodeFail } from '../interface.uts'
function callFail(options: any, errCode: number, errMsg: string) {
const err: QrCodeFail = {
errCode,
errMsg
}
options.fail?.(err)
options.complete?.(err)
}
export const createQrCode: CreateQrCode = function (options) {
if (options.text.trim().length == 0) {
callFail(options, 3001, 'createQrCode:fail text is empty')
return
}
const size = options.size != null ? options.size! : 512
try {
const matrix = new MultiFormatWriter().encode(
options.text,
BarcodeFormat.QR_CODE,
size,
size
)
const bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
for (let x = 0; x < size; x++) {
for (let y = 0; y < size; y++) {
bitmap.setPixel(x, y, matrix.get(x, y) ? Color.BLACK : Color.WHITE)
}
}
const stream = new ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream)
const imageBase64 = Base64.encodeToString(stream.toByteArray(), Base64.NO_WRAP)
bitmap.recycle()
const res: QrCodeResult = {
base64: 'data:image/png;base64,' + imageBase64,
width: size,
height: size
}
options.success?.(res)
options.complete?.(res)
} catch (e) {
callFail(options, 3002, 'createQrCode:fail ' + e)
}
}这段代码里有两类 import:
ts
import BarcodeFormat from 'com.google.zxing.BarcodeFormat'
import MultiFormatWriter from 'com.google.zxing.MultiFormatWriter'这是第三方 SDK 的类,来自 config.json 里的 Maven 依赖。
ts
import Bitmap from 'android.graphics.Bitmap'
import Base64 from 'android.util.Base64'这是 Android 系统类,用来把二维码矩阵转换成图片。
第 5 步:写 Web 兜底
文件:
text
uni_modules/uts-qrcode-zxing/utssdk/web/index.uts代码:
ts
import { CreateQrCode, QrCodeFail } from '../interface.uts'
export const createQrCode: CreateQrCode = function (options) {
const err: QrCodeFail = {
errCode: 4001,
errMsg: 'createQrCode:fail current platform is not supported'
}
options.fail?.(err)
options.complete?.(err)
}如果确实要支持 Web,可以在 web/index.uts 中改用 Web 端二维码库。但不要让 Android SDK 的实现硬跑到 Web 端。
第 6 步:uni-app 页面调用
vue
<template>
<view class="page">
<textarea v-model="text" class="textarea" />
<button type="primary" @click="makeQrCode">生成二维码</button>
<image v-if="qrCode" class="qrcode" :src="qrCode" mode="widthFix" />
<view v-if="errorText" class="error">{{ errorText }}</view>
</view>
</template>
<script>
import { createQrCode } from '@/uni_modules/uts-qrcode-zxing'
export default {
data() {
return {
text: 'https://uniapp.dcloud.net.cn/plugin/uts-plugin.html',
qrCode: '',
errorText: ''
}
},
methods: {
makeQrCode() {
this.errorText = ''
createQrCode({
text: this.text,
size: 480,
success: (res) => {
this.qrCode = res.base64
},
fail: (err) => {
this.qrCode = ''
this.errorText = err.errMsg
}
})
}
}
}
</script>
<style>
.page {
padding: 32rpx;
}
.textarea {
width: 100%;
min-height: 180rpx;
padding: 24rpx;
box-sizing: border-box;
border: 1px solid #ddd;
margin-bottom: 24rpx;
}
.qrcode {
width: 480rpx;
margin-top: 32rpx;
}
.error {
margin-top: 24rpx;
color: #d93025;
}
</style>页面仍然没有直接使用 ZXing。页面只调用 createQrCode()。
如果 SDK 只有本地 AAR
有些厂商 SDK 不提供 Maven 依赖,只给:
text
vendor-sdk.aar
vendor-helper.jar这时放到:
text
uni_modules/插件名/utssdk/app-android/libs/
vendor-sdk.aar
vendor-helper.jar然后在 config.json 中补齐其他要求,例如 minSdkVersion、abis、仓库、权限。UTS 代码里仍然按 SDK 文档导入类:
ts
import VendorSdk from 'com.vendor.sdk.VendorSdk'如果本地 AAR 里还依赖其他 Maven 包,也要把这些依赖写进 config.json.dependencies。只放一个 AAR,不代表它的传递依赖都会自动完整。
如果 SDK 需要初始化
很多真实 SDK 不是直接调用函数,而是先初始化:
text
initSdk(appKey)
-> login()
-> pay()
-> logout()UTS 插件里推荐拆成两个 API:
ts
export function initVendorSdk(options) {}
export function callVendorApi(options) {}不要在每次 callVendorApi() 里偷偷初始化。这样会带来三个问题:
- 初始化失败和业务失败混在一起。
- 多次调用可能重复初始化。
- 页面不知道 SDK 当前状态。
更稳的方式:
text
App 启动或进入业务页
-> initVendorSdk()
-> 初始化成功后再调用业务 API第三方 SDK 接入检查清单
- [ ] SDK 是否支持 Maven 依赖
- [ ]
config.json.dependencies是否写完整 - [ ] SDK 仓库是否写到
project.repositories - [ ] SDK 要求的
minSdkVersion是否满足 - [ ] SDK 要求的
abi是否和 App 保持一致 - [ ] SDK 要求的权限是否写进
AndroidManifest.xml - [ ] SDK 要求的 appKey / 授权文件是否有明确放置位置
- [ ] UTS 代码是否只向页面返回普通对象、字符串、数字、数组等可桥接数据
- [ ] 是否给非 Android 平台写了兜底实现
- [ ] 真机验证过,不只看 HBuilderX 语法提示
多插件互相引用
一个 UTS 插件可以引用另一个 uni_modules 下的 UTS 插件。
假设:
text
uni_modules/
uts-plugin-a/
uts-plugin-b/uts-plugin-a 中调用 uts-plugin-b:
ts
import { sthFromPluginB } from '@/uni_modules/uts-plugin-b'
export function sthFromPluginA(): string {
return sthFromPluginB()
}uts-plugin-a/package.json 需要声明依赖:
json
{
"uni_modules": {
"dependencies": ["uts-plugin-b"]
}
}注意:
- 必须用
@/uni_modules/xxx这种绝对路径。 - 不要写相对路径,例如
../../uni_modules/xxx。 - iOS 插件被其他插件引用时,如果内部依赖三方库,还要额外遵守 iOS 的实现限制。
常见坑
| 问题 | 常见原因 | 处理建议 |
|---|---|---|
页面里直接导入 index.uts 报错 | 绕过了插件入口 | 只从 @/uni_modules/插件名 导入 |
| 只写 Android 后 Web 运行报错 | 没有 Web 兜底 | 增加 utssdk/web/index.uts 或明确平台限制 |
| 插件返回值页面不好处理 | 返回结构不稳定 | 用 interface.uts 固定参数和返回类型 |
| 多个插件依赖同一个 SDK 冲突 | 都把 jar / aar 放进 libs | 优先用仓库依赖 |
| 第三方 SDK 类导入失败 | config.json.dependencies 或仓库没写完整 | 先按 SDK 官方 Gradle 接入方式补齐依赖 |
| SDK 初始化状态混乱 | 每个业务 API 都偷偷初始化 | 单独封装 initSdk() |
| 页面拿到 Android 原生对象后无法使用 | 插件直接返回 SDK 对象 | 转成普通对象再返回 |
| 本地调试直接使用 so 不正常 | UTS 本地调试不支持直接用 so | 封装成 AAR,或按官方方式集成 so 和 jar |
| 复杂对象数组传参异常 | uni-app JS 到 UTS 桥接有类型限制 | 复杂数组用 any[] 承接,再在 UTS 中处理 |
| 想用 CLI 创建 UTS 插件 | 当前官方文档不支持 | 使用 HBuilderX 创建和使用 |
推荐开发顺序
- 先判断是 API 插件还是组件插件。
- 在
uni_modules下创建 UTS 插件。 - 写
package.json,确认插件 ID。 - 写
interface.uts,先定参数、返回值、错误结构。 - 先实现一个平台,例如 Android。
- 给其他平台写真实实现或明确失败兜底。
- 在 uni-app 页面中只从插件根目录导入。
- 真机运行验证,不只看语法通过。
- 需要三方 SDK 时,优先使用
config.json配仓库依赖。 - 准备给别人用时,写清平台支持、权限、配置文件、离线打包注意事项。
学习路线
如果只是使用别人写好的 UTS 插件:
text
看插件 README
-> import 插件根目录
-> 按示例调用
-> 真机验证如果要自己写 API 插件:
text
先写 interface.uts
-> 再写 app-android/index.uts
-> 再补 web 或其他平台兜底
-> 页面调用
-> 真机调试如果要封装三方 SDK:
text
确认 SDK 官方接入方式
-> 判断能否使用仓库依赖
-> 写 config.json
-> 封装最小 API
-> 再逐步扩展能力和离线 SDK 的关系
UTS 插件开发文档主要讲的是插件如何创建、实现、调用、打包。
如果项目使用 Android 离线 SDK,还需要额外处理原生工程接入问题,例如:
utsplugin-release.aar- Android Library 模块
dcloud_uniplugins.jsonUTSHooksClassArrayconfig.json手动合并
这部分可以看:uni-app Android 离线 SDK 接入 UTS 插件。