Skip to content

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 编译方向典型用途
WebJavaScript做 Web 兜底或模拟实现
AndroidKotlin调 Android 系统 API、三方 SDK
iOSSwift调 iOS 系统 API、三方 SDK
HarmonyOSArkTS调 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.jsUTS 插件
调用方式JS 通过反射调用系统 APIUTS 编译到原生方向
性能和稳定性受反射和运行环境影响App 端是真正原生执行
类型约束更强
维护成本容易写出难排查的问题结构更清晰
推荐程度适合少量临时能力更适合正式插件化封装

Native.js 像是在 JS 里临时伸手调用原生,UTS 插件更像是把原生能力整理成一个正式模块。

和旧版 App 原生语言插件对比

对比项App 原生语言插件UTS 插件
开发语言Java / Objective-C 等UTS
开发工具Android Studio / XcodeHBuilderX 为主
前端调用uni.requireNativePlugin()普通 import
支持项目uni-appuni-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.utsAndroid 平台实现
utssdk/app-ios/index.utsiOS 平台实现
utssdk/web/index.utsWeb 平台实现或兜底

入口选择规则:

情况实际使用哪个入口
当前平台有分平台 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)
}

这段代码做了三件事:

  1. 通过 UTSAndroid.getAppContext() 获取 Android 上下文。
  2. 通过系统电池广播读取电量和充电状态。
  3. 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.utsAndroid 平台具体实现
config.jsonGradle 依赖、仓库、最低系统版本、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”,而是在插件内部完成三件事:

  1. config.json 里声明 SDK 依赖。
  2. app-android/index.uts 里导入 SDK 的 Java / Kotlin 类。
  3. 把 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适合底层算法、硬件厂商 SDKUTS 本地调试直接使用 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 的依赖语句
minSdkVersionSDK 要求的最低 Android 版本
project.repositoriesSDK 所在的 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、宽度、高度
  • 错误:统一返回 errCodeerrMsg

第 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 中补齐其他要求,例如 minSdkVersionabis、仓库、权限。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 创建和使用

推荐开发顺序

  1. 先判断是 API 插件还是组件插件。
  2. uni_modules 下创建 UTS 插件。
  3. package.json,确认插件 ID。
  4. interface.uts,先定参数、返回值、错误结构。
  5. 先实现一个平台,例如 Android。
  6. 给其他平台写真实实现或明确失败兜底。
  7. 在 uni-app 页面中只从插件根目录导入。
  8. 真机运行验证,不只看语法通过。
  9. 需要三方 SDK 时,优先使用 config.json 配仓库依赖。
  10. 准备给别人用时,写清平台支持、权限、配置文件、离线打包注意事项。

学习路线

如果只是使用别人写好的 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.json
  • UTSHooksClassArray
  • config.json 手动合并

这部分可以看:uni-app Android 离线 SDK 接入 UTS 插件

资料来源

基于 VitePress 的个人知识库骨架