UniApp 原生插件开发入门:Android 与 iOS 桥接
结论先行:UniApp 调用原生能力的入口是"模块(Module)":JS 层调用 uni.requireNativePlugin 或 uts 导入模块,框架通过桥接层把调用转给 Android 的 UniModule 或 iOS 的 DCUniModule,再把结果异步回调回 JS。新项目优先考虑 uts 插件——用类 TypeScript 语法写,编译期生成原生代码,免写桥接;只有依赖现成原生 SDK 库文件时才走传统原生插件路线。开发调试绕不开自定义基座,云打包有明确限制要提前知晓。
什么时候需要原生插件
先建立"能不写就不写"的判断:uni 统一 API 已覆盖蓝牙、NFC、传感器、相机、定位等常见硬件能力,业务能用统一 API 解决的不要碰原生。真正需要插件的场景有三类:
- 硬件 SDK 依赖:热敏打印机、扫码枪、指纹门禁等厂商只提供 aar/framework 形式的原生 SDK。
- 深度定制:统一 API 的参数粒度不够,例如需要精细控制 BLE(Bluetooth Low Energy)广播包格式。
- 性能敏感计算:音视频编解码、加密运算等放在原生层执行更高效。
两种插件形态:uts 插件与传统原生插件
| 维度 | uts 插件 | 传统原生插件(本地/市场插件) |
|---|---|---|
| 编写语言 | uts(TypeScript 语法)编译为 Kotlin/Swift | Java/Kotlin + Objective-C/Swift |
| 桥接代码 | 无需手写,编译器生成 | 需按 UniModule/DCUniModule 规范封装 |
| 集成方式 | 源码随项目编译 | aar/jar/framework 打包,或插件市场引用 |
| 调试体验 | 可断点原生代码(以官方文档为准) | 依赖自定义基座,黑盒程度更高 |
选型口诀:新能力用 uts 从零写,存量 SDK 用传统插件包装。两者可以在同一个项目共存。
Android 端:三步封装一个 Module
传统原生插件的 Android 侧核心是三步:继承 UniModule、方法加注解、注册插件名。
// 1. 继承 UniModule,方法加 @UniJSMethod 注解即可暴露给 JS 层
public class PrinterModule extends UniModule {
@UniJSMethod(uiThread = false)
public void printText(JSONObject options, UniJSCallback callback) {
String text = options.getString("text");
// 2. 在子线程执行耗时硬件操作,避免阻塞 UI
boolean ok = PrinterSdkHolder.get().print(text);
JSONObject result = new JSONObject();
result.put("success", ok);
// 3. 通过 UniJSCallback 把结果异步回传 JS
if (callback != null) callback.invoke(result);
}
}// dcloud_uniplugins.json:注册模块名,JS 层用它 require
{
"nativePlugins": [{
"plugins": [{
"type": "module",
"name": "PrinterModule",
"class": "com.example.printer.PrinterModule"
}]
}]
}JS 侧调用:const printer = uni.requireNativePlugin('PrinterModule'); printer.printText({ text: '小票内容' }, res => {})。注意生命周期:模块内通过 mUniSDKInstance 拿上下文,页面销毁时要释放硬件资源,避免持有多余引用。
iOS 端与 Pod 集成要点
iOS 侧的对应物是 DCUniModule:创建继承 DCUniModule 的类,用 @objc(printText:) 暴露方法,通过 uniSendResult 回调。三方库集成走 Pod:插件的 package.json 中声明 pod 依赖,打包时自动执行 pod install。高频坑点有三个:一是 iOS 的方法名与 JS 参数要一一对应,参数类型映射以官方文档的类型对照表为准;二是权限声明(Info.plist)必须补齐,否则调用相机、麦克风直接崩溃;三是真机调试需开发者证书,模拟器不支持部分硬件能力(蓝牙外设、NFC 等)。
调试与打包:自定义基座与云打包限制
原生插件无法用标准基座运行,调试流程固定为:插件放入项目的 uni_modules 或 nativeplugins 目录 → HBuilderX 制作"自定义基座" → 真机运行选中自定义基座。这意味着每次改原生代码都要重新打基座,把原生层接口设计稳定、业务迭代只动 JS 层,是控制联调成败的关键。
云打包的四条限制提前记好:一是本地原生插件打云包时随包编译,但依赖的 aar/framework 要自包含,缺符号链接会在打包期报错;二是插件市场引用的付费插件要注意授权与版本匹配;三是原生层变更(新增插件、升级 SDK)后,正式包必须整包更新,热更(wgt)不覆盖原生层;四是 iOS 云打包不支持在插件里动态下载执行代码,上架审核也不允许此类行为。
以猫拽低代码(CatPull)为例,其采用插件化架构管理平台自身能力(属性编辑、数据源、云函数等 30+ 内置插件),这套"插件注册、生命周期、事件总线"的组织方式同样值得原生插件开发借鉴:把每个硬件能力封装成独立插件、用统一事件对外暴露,业务层面对的是稳定的事件接口,底层换 SDK 时不影响业务代码——这与低代码平台的物料与插件解耦思想一脉相承。
FAQs
Q1:uts 插件和传统原生插件可以混用吗?
A:可以。同一项目的 uni_modules 中可以同时存在 uts 插件与传统原生插件,建议新能力用 uts、存量 SDK 用传统插件封装,注意各自的打包与调试流程独立进行。
Q2:修改了原生插件代码,为什么要重新打自定义基座?
A:原生代码参与基座包的编译,标准基座里没有你的插件。任何原生层变更都要重新制作自定义基座并重新安装到真机,纯 JS 层修改则热刷新即可。
Q3:云打包的应用怎么更新原生插件?
A:原生层变更必须重新云打包并整包发布(应用市场更新或 apk 覆盖安装),wgt 热更新只覆盖前端资源,无法更新原生代码与原生 SDK。
延伸阅读
- 低代码技术要求——评估平台扩展能力时该看的技术项
- 低代码平台架构对比——插件化架构如何支撑平台扩展
- UniApp 的优势——UniApp 生态中原生能力接入的整体图景
