uni-app Android 离线 SDK 接入 UTS 插件
这篇文章解决什么问题
uni-app 项目使用 UTS 插件后,云端打包通常不用手动处理太多 Android 工程细节;但换成 Android 离线 SDK 后,很多事情需要自己补齐:
utsplugin-release.aar没有放进原生工程- UTS 插件导出的
uni_modules没有变成 Android Library 模块 - 插件的
config.json没有手动合并到 Gradle - 组件插件没有写进
dcloud_uniplugins.json hooksClass没有写进UTSHooksClassArray- HBuilderX、离线 SDK、Gradle、Kotlin 版本混用
官方文档的问题是:同一条配置链路里混入了 uni-app x 的模块化写法。uni-app 能参考其中的 UTS 插件配置规则,但不能照搬 uni-app x 的主模块接入方式。
本文只讨论 uni-app,不整理 uni-app x。
适用范围
适用于下面这种情况:
- 项目是
uni-app,不是uni-app x - Android 使用离线 SDK 或原生工程集成方式
- 项目里使用了 UTS 插件
- 插件需要在 Android Studio 工程中一起编译、运行、打包
不适用于:
- 只使用云端打包
- 只接入传统 App 原生语言插件
- uni-app x 原生 SDK 接入
- 只开发 UTS 插件源码,不做离线打包
先说结论
uni-app Android 离线 SDK 接入 UTS 插件,本质上要补齐三件事:
| 层级 | 要做什么 | 目的 |
|---|---|---|
| App 主工程 | 引入 UTS 基础运行依赖 | 让 APK 具备运行 UTS 插件的基础能力 |
| UTS 插件模块 | 把导出的插件源码、资源、依赖变成 Android Library | 让插件代码参与 Android 编译 |
| uni-app 注册信息 | 写 dcloud_uniplugins.json 和 UTSHooksClassArray | 让运行时知道有哪些组件和生命周期监听类 |
最容易错的点是:uni-app 不需要在 UTS 插件模块里配置 io.dcloud.uts.kotlin 插件。官方 UTS 插件配置文档也明确写了,uni-app 可以忽略这个 Gradle 插件配置。
先分清三个名字
UTS
UTS 是 uni type script。它语法上接近 TypeScript,在 Android 平台会编译到 Kotlin 方向,目的是让前端项目可以封装原生能力。
理解它时不要把它当成“运行在 WebView 里的 JS”。在 App 端,它最终会进入原生编译链路。
UTS 插件
UTS 插件 是放在 uni-app 工程里的 uni_modules 插件。它面向前端使用,通常通过 import 或组件标签调用。
UTS 插件分两类:
| 类型 | 使用方式 | 离线接入重点 |
|---|---|---|
| API 插件 | 在 script 中调用 | 重点是模块依赖、资源、权限、生命周期 |
| 组件插件 | 在 template 中使用 | 除了模块依赖,还要写 dcloud_uniplugins.json |
Android UTS 插件模块
Android UTS 插件模块 是 Android Studio 工程中的一个 Android Library 模块。
它不是前端插件本身,而是把导出的 UTS 插件资源手动放进 Android 工程后形成的原生模块。离线打包时,每个需要原生编译的 UTS 插件,建议对应一个 Android Library 模块。
整体流程
完整链路可以按下面顺序处理:
text
uni-app 项目
-> HBuilderX 生成本地打包 App 资源
-> 导出 assets/apps/{appid}
-> 导出 uni_modules 中的 UTS 插件资源
-> Android Studio 主工程引入 UTS 基础能力
-> 每个 UTS 插件创建一个 Android Library 模块
-> 合并 config.json、资源、Manifest、源码
-> 注册组件和 hooks
-> 构建 APK 并真机验证这个顺序不要反过来。先确认 HBuilderX 导出的资源里确实包含 UTS 插件,再动 Android 工程。
第 1 步:确认版本和授权
HBuilderX 与离线 SDK
官方离线 SDK 文档要求使用 UTS 插件时,HBuilderX 版本至少为 4.18。同时,离线 SDK 与 HBuilderX 版本要尽量保持一致。
不要只升级 HBuilderX,不更新 Android 离线 SDK。UTS 基础 AAR、内置模块 AAR、assets/data 都可能随版本变化。
付费插件授权
如果使用插件市场的加密付费 UTS 插件,要确认授权类型。
官方 UTS 插件配置文档提示:普通授权版加密付费 UTS 插件不支持通过原生 SDK 打包,需要拿到插件源码,通常应购买源码授权版。
建议结论:
| 方案 | 优点 | 缺点 | 建议 |
|---|---|---|---|
| 源码授权版 | 可进原生 SDK 离线打包链路,问题可排查 | 成本更高 | 推荐 |
| 普通授权加密版 | 接入成本低 | 离线 SDK 场景不可控,可能无法打包 | 不推荐用于离线打包 |
第 2 步:生成 uni-app 本地打包资源
在 HBuilderX 中执行:
text
发行 -> 原生App-本地打包 -> 生成本地打包App资源生成后重点检查这些内容:
text
unpackage/
resources 或 resource/
app-android/
apps/{appid}/
uni_modules/不同 HBuilderX 版本导出目录名可能略有差异,判断标准不是目录名,而是有没有这两类内容:
apps/{appid}:uni-app 运行资源uni_modules:UTS 插件导出的 Android 资源
如果没有 uni_modules,通常说明当前 uni-app 项目没有导出 UTS 插件资源,或 HBuilderX / 插件版本不符合要求。
第 3 步:确认普通离线 SDK 已经能跑
接入 UTS 前,先确认普通 uni-app 离线 SDK 工程可以运行。
最低检查项:
SDK/assets/data已复制到app/src/main/assets/data- 导出的
apps/{appid}已复制到app/src/main/assets/apps/{appid} dcloud_control.xml中的appid与manifest.json、assets/apps/{appid}目录一致gradle.properties已开启 AndroidX
properties
android.useAndroidX=true
android.enableJetifier=true如果普通离线 SDK 都不能启动,不要继续接 UTS。先把基础工程跑通,否则后面的错误会混在一起。
第 4 步:在 App 主工程加入 UTS 基础能力
复制基础 AAR
从 Android 离线 SDK 中找到:
text
SDK/libs/utsplugin-release.aar复制到 Android 工程:
text
app/libs/utsplugin-release.aar添加 App 级依赖
在 app/build.gradle 中确认有本地 AAR 引入,例如:
groovy
dependencies {
implementation fileTree(include: ['*.jar', '*.aar'], dir: 'libs')
}然后加入 UTS 基础依赖。版本要优先参考当前离线 SDK 对应的官方文档,不要混用旧文档里的版本。
groovy
dependencies {
implementation "com.squareup.okhttp3:okhttp:3.12.12"
implementation "androidx.core:core-ktx:1.6.0"
implementation "org.jetbrains.kotlin:kotlin-stdlib:2.2.0"
implementation "org.jetbrains.kotlin:kotlin-reflect:2.2.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1"
implementation "com.github.getActivity:XXPermissions:18.63"
}添加 JitPack 仓库
在项目根 build.gradle 或当前工程实际使用的仓库配置位置加入:
groovy
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}如果项目使用较新的 settings.gradle 统一管理仓库,则放到 dependencyResolutionManagement.repositories 中。
第 5 步:为每个 UTS 插件创建 Android Library 模块
在 Android Studio 中创建模块:
text
File -> New -> New Module -> Android Library建议:
| 项目 | 建议值 |
|---|---|
| Module name | 与 UTS 插件 ID 一致,例如 uts-nativepage |
| Language | Kotlin |
| Build configuration language | Groovy DSL |
| Minimum SDK | 不低于插件 config.json 的 minSdkVersion |
创建后,在 settings.gradle 中确认包含模块:
groovy
include ':uts-nativepage'再在 app/build.gradle 中添加:
groovy
dependencies {
implementation project(':uts-nativepage')
}多个 UTS 插件就创建多个模块,并逐个加入主工程。
第 6 步:配置 Android UTS 插件模块
不要添加 io.dcloud.uts.kotlin
uni-app 场景下,UTS 插件模块不要照搬 uni-app x 的 Gradle 插件配置。
也就是说,不要加:
groovy
plugins {
id 'io.dcloud.uts.kotlin'
}这是 uni-app x 文档中最容易误导 uni-app 项目的地方。
插件模块依赖
UTS 插件模块编译时需要看到 App 主工程里的基础 AAR,也需要看到插件自身的本地库。
示例:
groovy
dependencies {
compileOnly fileTree(include: ['*.aar'], dir: '../app/libs')
compileOnly fileTree(include: ['*.aar', '*.jar'], dir: './libs')
compileOnly 'com.alibaba:fastjson:1.1.46.android'
compileOnly 'androidx.core:core-ktx:1.6.0'
compileOnly 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.0'
compileOnly 'org.jetbrains.kotlin:kotlin-reflect:1.6.0'
compileOnly 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8'
compileOnly 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.8'
}这里的关键不是死记版本,而是理解两点:
- 插件模块使用
compileOnly,避免把主工程已有依赖重复打包 - 版本要与当前离线 SDK、已有工程保持一致,发现冲突时以当前 SDK 文档和主工程依赖为准
如果插件依赖另一个 UTS 插件,先把被依赖插件也建成 Android Library 模块,再添加模块依赖:
groovy
dependencies {
implementation project(':uts-other-plugin')
}第 7 步:按 config.json 合并原生配置
UTS 插件的 Android 配置通常位于:
text
uni_modules/{plugin-id}/utssdk/app-android/config.json离线 SDK 不会自动替你合并这些配置,需要手动处理。
典型结构如下:
json
{
"abis": ["armeabi-v7a", "arm64-v8a"],
"dependencies": [
"androidx.core:core-ktx:1.6.0",
{
"id": "com.xxx.richtext:richtext",
"source": "implementation 'com.xxx.richtext:richtext:3.0.7'"
}
],
"minSdkVersion": 21,
"project": {
"plugins": ["com.huawei.agconnect"],
"dependencies": ["com.huawei.agconnect:agcp:1.6.0.300"],
"repositories": [
"maven { url 'https://artifact.example.com/repository/' }"
]
},
"components": [
{
"name": "zl-text",
"class": "uts.sdk.modules.zlText.ZlTextComponent"
}
],
"hooksClass": "uts.sdk.modules.zlText.ZlTextHook"
}逐项处理:
| 字段 | 放到哪里 | 处理方式 |
|---|---|---|
abis | App 主模块和插件模块 | 加到 ndk.abiFilters |
minSdkVersion | App 主模块和插件模块 | 调整最低 Android 版本 |
dependencies | App 主模块和插件模块 | 字符串拼 implementation,对象使用 source |
project.plugins | 插件模块 | 加到 plugins |
project.dependencies | 项目根 Gradle | 加到 buildscript.dependencies |
project.repositories | 仓库配置 | 加到 settings.gradle 或根 Gradle 仓库配置 |
components | app/src/main/assets/dcloud_uniplugins.json | 注册 uni-app 组件 |
hooksClass | app/build.gradle | 写入 UTSHooksClassArray |
abis
App 主模块和插件模块都要处理。只给插件模块加不够。
groovy
android {
defaultConfig {
ndk {
abiFilters "armeabi-v7a", "arm64-v8a"
}
}
}dependencies
字符串写法:
json
"androidx.core:core-ktx:1.6.0"合并成:
groovy
dependencies {
implementation 'androidx.core:core-ktx:1.6.0'
}对象写法:
json
{
"id": "com.xxx.richtext:richtext",
"source": "implementation 'com.xxx.richtext:richtext:3.0.7'"
}只取 source:
groovy
dependencies {
implementation 'com.xxx.richtext:richtext:3.0.7'
}components
这是 uni-app 和 uni-app x 最重要的差异之一。
uni-app 中注册 UTS 组件,不写 UTSRegisterComponents,而是写:
text
app/src/main/assets/dcloud_uniplugins.json如果文件不存在,手动创建。
示例:
json
{
"nativePlugins": [
{
"plugins": [
{
"type": "component",
"name": "zl-text",
"class": "uts.sdk.modules.zlText.ZlTextComponent"
}
]
}
]
}如果已经有传统原生插件或其他 UTS 组件,不要覆盖原文件,要把 plugins 数组合并。
错误做法:
json
{
"nativePlugins": [
{
"plugins": [
{
"type": "component",
"name": "zl-text",
"class": "uts.sdk.modules.zlText.ZlTextComponent"
}
]
}
]
}这段本身没错,但如果直接替换已有文件,会导致原有插件丢失。
推荐合并:
json
{
"nativePlugins": [
{
"plugins": [
{
"type": "module",
"name": "oldNativePlugin",
"class": "com.example.OldNativePlugin"
},
{
"type": "component",
"name": "zl-text",
"class": "uts.sdk.modules.zlText.ZlTextComponent"
}
]
}
]
}hooksClass
如果 config.json 里有:
json
"hooksClass": "uts.sdk.modules.zlText.ZlTextHook"在 app/build.gradle 的 defaultConfig 中加入:
groovy
android {
defaultConfig {
buildConfigField 'String[]', 'UTSHooksClassArray', '{"uts.sdk.modules.zlText.ZlTextHook"}'
}
}多个 hooks 要合并到同一个数组里:
groovy
android {
defaultConfig {
buildConfigField 'String[]', 'UTSHooksClassArray', '{"uts.sdk.modules.zlText.ZlTextHook","uts.sdk.modules.other.OtherHook"}'
}
}不要写多个 UTSHooksClassArray 覆盖彼此。
另外,官方文档提示暂不支持在 build.gradle 中设置 applicationIdSuffix,否则可能导致组件初始化失败。离线打包工程有多渠道或多环境配置时,这一点要单独检查。
第 8 步:复制 UTS 插件资源
以插件目录为准:
text
uni_modules/{plugin-id}/utssdk/app-android/按下面规则复制到 Android UTS 插件模块:
| 插件目录 | 复制到 Android 模块 | 说明 |
|---|---|---|
libs | {module}/libs | 同时检查 App 主模块是否也需要引入 |
assets | {module}/src/main/assets | 没有则跳过 |
res | {module}/src/main/res | 没有则跳过 |
AndroidManifest.xml | {module}/src/main/AndroidManifest.xml | 如果有 package,建议移到 namespace |
src | {module}/src/main/java | 保持原有包目录结构 |
注意两点:
- 插件自己的
assets、res是插件内置资源,不等于 App 主工程的assets/apps/{appid}。 - 如果插件依赖三方 SDK 的授权文件,通常应该按插件文档放到 App 主工程指定位置,不要擅自塞进插件目录。
第 9 步:处理 AndroidManifest.xml
如果插件有自己的 AndroidManifest.xml,需要放到插件模块:
text
{module}/src/main/AndroidManifest.xml如果里面有权限、activity、service、provider,要确认合并结果符合主工程要求。
常见检查项:
- 权限是否声明完整
activity是否需要android:exportedprovider的 authorities 是否和应用包名冲突- 旧式
package是否已经改由模块namespace管理
Android Gradle Plugin 新版本更推荐在 build.gradle 中写:
groovy
android {
namespace 'uts.sdk.modules.zlText'
}不要在多个插件模块里复用同一个 namespace。
第 10 步:构建和真机验证
Gradle Sync
先执行 Sync Project with Gradle Files。
重点看:
- 依赖仓库是否能访问
- 是否有重复类
- 是否有 Kotlin / AndroidX 版本冲突
- 本地 AAR 是否路径错误
构建 APK
执行 Build APK 或命令行构建。
重点看:
utsplugin-release.aar是否进入 App 主模块- 每个 UTS 插件模块是否被
implementation project(...)引入 config.json中的依赖是否全部手动合并minSdkVersion、abiFilters是否与插件要求一致
真机验证
API 插件验证:
- 页面能正常
import插件 - 调用后能收到成功或失败回调
- Android Studio Logcat 能看到插件日志
组件插件验证:
- 页面能渲染组件标签
dcloud_uniplugins.json在 APK 的assets中存在name和前端 template 中使用的组件名一致class能在插件模块源码或编译产物中找到
hooks 验证:
- App 启动时能看到 hook 日志
- 隐私协议、权限申请、生命周期回调按预期触发
- 多个 hooks 都生效,不是只有最后一个生效
常见问题
1. UTS 插件开发时正常,离线打包后找不到插件
优先检查:
utsplugin-release.aar是否在app/libsapp/build.gradle是否引入了本地 AAR- UTS 插件模块是否写进
settings.gradle app/build.gradle是否implementation project(':插件模块名')- HBuilderX 导出的
uni_modules是否已经复制进对应模块
2. UTS 组件标签不显示
优先检查:
components是否写到了app/src/main/assets/dcloud_uniplugins.jsontype是否是componentname是否和前端组件标签一致class是否写完整包名- 是否误写成了 uni-app x 的
UTSRegisterComponents - 是否设置了
applicationIdSuffix
3. 生命周期 hook 不执行
优先检查:
config.json是否存在hooksClassUTSHooksClassArray是否写在 App 主模块defaultConfig- 多个 hook 是否合并在同一个数组
- 字符串里的引号和转义是否被删错
4. Gradle 报重复类或依赖冲突
常见原因:
- 同一个 AAR 同时放在 App 主模块和插件模块,并且都用
implementation - 插件的
libs和config.json.dependencies引入了同一个 SDK - 主工程已有 AndroidX / Kotlin 依赖,插件又引入了不兼容版本
处理建议:
- 插件模块优先使用
compileOnly - 能用仓储依赖就不要手动放重复 AAR
- 以当前离线 SDK 官方依赖版本为基准
5. 文档里 Kotlin 版本不一致
官方多份文档更新时间不同,示例版本可能不完全一致。处理原则:
- App 主模块的 UTS 基础依赖,优先看当前 Android 离线 SDK 的 UTS 基础模块文档。
- 插件模块的
compileOnly依赖,以能与当前主工程、当前 SDK 编译通过为准。 - 不要在同一个工程里混用多个 Kotlin 大版本。
6. 普通授权版付费 UTS 插件无法离线打包
这是授权和产物形态问题,不是 Gradle 小配置能解决的问题。
离线 SDK 需要能进入原生编译链路的插件资源。加密付费普通授权版不适合这种场景,应使用源码授权版。
推荐检查清单
接入完成后,按下面清单复核:
- [ ] HBuilderX 与 Android 离线 SDK 版本一致或明确兼容
- [ ] 普通 uni-app 离线工程已经能启动
- [ ]
app/libs/utsplugin-release.aar已存在 - [ ] App 主工程已加入 UTS 基础依赖
- [ ] 项目仓库配置包含 JitPack
- [ ] 每个 UTS 插件都有对应 Android Library 模块
- [ ] uni-app 场景没有配置
io.dcloud.uts.kotlin - [ ] 插件
config.json已逐项合并 - [ ] 组件插件已写入
dcloud_uniplugins.json - [ ]
hooksClass已合并到UTSHooksClassArray - [ ] 插件
libs/assets/res/AndroidManifest.xml/src已按目录复制 - [ ] App 主模块已
implementation project(':插件模块名') - [ ] 真机验证 API、组件、hooks 均正常
实际执行顺序
如果从零开始处理,推荐按这个顺序:
- 先让普通 uni-app 离线 SDK 工程跑起来。
- 更新 HBuilderX 和 Android 离线 SDK,保持版本匹配。
- 重新生成本地打包 App 资源。
- 检查导出资源里是否有
uni_modules。 - 把
utsplugin-release.aar和 UTS 基础依赖加入 App 主工程。 - 每个 UTS 插件创建一个 Android Library 模块。
- 把插件源码、资源、Manifest、libs 复制到对应模块。
- 读取插件
config.json,逐项合并到 Android 工程。 - 组件插件写
dcloud_uniplugins.json。 - 有
hooksClass的插件写UTSHooksClassArray。 - Gradle Sync。
- 构建 APK。
- 真机验证。