UniApp 原生插件开发完整指南
目录
1. 准备工作
1.1 开发环境要求
| 工具 | 版本要求 | 说明 |
|---|---|---|
| IDE | Android Studio 或更高 | 官方推荐 |
| JDK | JDK 17+ | Android Studio 2024+ 要求 |
| Gradle | 8.12+ | 项目根目录使用 |
| HBuilderX | 最新稳定版 | uniapp 前端开发 |
| Android SDK | API 35 | compileSdkVersion |
| NDK | 可选 | 仅需支持 arm64-v8a |
1.2 项目结构概览
真正需要的是UniPlugin-Hello-AS

uniapp-native/
├── app/ # 主应用模块(入口)
│ ├── build.gradle
│ └── src/main/
│ ├── AndroidManifest.xml
│ ├── assets/apps/ # uniapp 前端资源
│ └── java/
├── uniplugin_arcface/ # 虹软人脸识别插件 ★
├── uniplugin_module/ # 模块插件示例
├── uniplugin_component/ # 组件插件示例
├── uniplugin_richalert/ # 富文本弹窗插件
├── uts-toast/ # Kotlin UTS插件
├── libs/ # 全局共享库
│ ├── arcsoft_face.jar
│ ├── arcsoft_image_util.jar
│ └── arm64-v8a/ # SO库(仅64位)
├── uniappDemo/ # uniapp前端示例
└── doc/ # 文档目录
2. 新增功能模块
2.1 创建模块目录结构
uniplugin_xxx/ # 模块名称(uniplugin_前缀)
└── src/main/
├── java/.../xxx/
│ ├── XxxModule.java # 核心模块类
│ └── util/... # 工具类
├── AndroidManifest.xml
└── build.gradle
2.2 创建模块 build.gradle
gradle
apply plugin: 'com.android.library'
android {
namespace 'io.dcloud.uniplugin.xxx' // 替换为你的包名
compileSdkVersion 35
buildToolsVersion '35.0.0'
defaultConfig {
minSdkVersion 21
targetSdkVersion 32
ndk {
abiFilters 'arm64-v8a' // 只保留64位架构
}
}
sourceSets {
main {
jniLibs.srcDirs = ['libs', '../libs']
}
}
// 排除不需要的架构
packagingOptions {
jniLibs {
excludes += ['**/armeabi-v7a/**', '**/x86/**', '**/x86_64/**']
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
repositories {
flatDir {
dirs 'libs', '../libs'
}
}
dependencies {
// UniApp核心库依赖
compileOnly fileTree(dir: '../app/libs', include: ['uniapp-v8-release.aar'])
// 你的SDK依赖
implementation files('../libs/your_sdk.jar')
// 基础库
compileOnly 'androidx.appcompat:appcompat:1.1.0'
compileOnly 'com.alibaba:fastjson:1.2.83'
}
2.3 创建模块 AndroidManifest.xml
xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<!-- 如果模块需要Activity,在此配置 -->
<!-- <activity android:name="xxx.Activity" /> -->
</application>
</manifest>
2.4 App 模块引入新模块
app/build.gradle:
gradle
dependencies {
// ... 其他依赖 ...
// 添加新模块
implementation project(':uniplugin_xxx')
}
3. 功能开发
3.1 创建 UniModule 类
继承 UniModule 类,使用 @UniJSMethod 注解暴露方法:
java
package io.dcloud.uniplugin.xxx;
import com.alibaba.fastjson.JSONObject;
import io.dcloud.feature.uniapp.annotation.UniJSMethod;
import io.dcloud.feature.uniapp.bridge.UniJSCallback;
import io.dcloud.feature.uniapp.common.UniModule;
/**
* 功能模块示例
*/
public class XxxModule extends UniModule {
private static final String TAG = "XxxModule";
// SDK 密钥配置(建议移到配置文件)
private static final String APP_KEY = "your_app_key";
/**
* 异步方法示例(在子线程执行)
*/
@UniJSMethod(uiThread = false)
public void asyncMethod(JSONObject options, UniJSCallback callback) {
try {
// 处理逻辑
String param = options.getString("param");
// 返回成功结果
JSONObject result = new JSONObject();
result.put("data", "处理结果");
callback.invoke(result);
} catch (Exception e) {
// 返回错误结果
JSONObject error = new JSONObject();
error.put("code", -1);
error.put("message", e.getMessage());
callback.invoke(error);
}
}
/**
* UI线程方法示例
*/
@UniJSMethod(uiThread = true)
public void uiMethod(JSONObject options, UniJSCallback callback) {
// 此方法在UI线程执行
}
/**
* 同步方法示例
*/
@UniJSMethod(uiThread = false)
public JSONObject syncMethod() {
JSONObject result = new JSONObject();
result.put("status", "ok");
return result;
}
/**
* 页面销毁时自动调用
*/
@Override
public void onActivityDestroy() {
super.onActivityDestroy();
// 释放资源
}
}
3.2 回调工具类
创建 CallbackUtil.java 统一处理回调:
java
package io.dcloud.uniplugin.xxx.util;
import com.alibaba.fastjson.JSONObject;
import io.dcloud.feature.uniapp.bridge.UniJSCallback;
public class CallbackUtil {
/**
* 执行成功回调
*/
public static void invokeSuccess(UniJSCallback callback, String message) {
invoke(callback, 0, message, null);
}
public static void invokeSuccess(UniJSCallback callback, String message, Object data) {
invoke(callback, 0, message, data);
}
/**
* 执行错误回调
*/
public static void invokeError(UniJSCallback callback, String message) {
invoke(callback, -1, message, null);
}
private static void invoke(UniJSCallback callback, int code, String message, Object data) {
if (callback == null) return;
JSONObject result = new JSONObject();
result.put("code", code);
result.put("message", message);
if (data != null) {
result.put("data", data);
}
callback.invoke(result);
}
}
3.3 业务开发示例(以 ArcFace 为例)
虹软模块核心代码结构:
java
public class ArcFaceModule extends UniModule {
// SDK 配置
private static final String APP_ID = "your_app_id";
private static final String SDK_KEY = "your_sdk_key";
private static final String ACTIVE_KEY = "your_active_key";
private FaceEngine faceEngine;
private boolean isEngineActive = false;
/**
* 初始化引擎
*/
@UniJSMethod(uiThread = false)
public void initEngine(JSONObject options, UniJSCallback callback) {
if (isEngineActive && faceEngine != null) {
CallbackUtil.invokeSuccess(callback, "引擎已初始化");
return;
}
faceEngine = new FaceEngine();
int activeCode = FaceEngine.activeOnline(
mUniSDKInstance.getContext(), ACTIVE_KEY, APP_ID, SDK_KEY
);
if (activeCode == ErrorInfo.MOK ||
activeCode == ErrorInfo.MERR_ASF_ALREADY_ACTIVATED) {
isEngineActive = true;
int initCode = faceEngine.init(
mUniSDKInstance.getContext(),
DetectMode.ASF_DETECT_MODE_IMAGE,
DetectFaceOrientPriority.ASF_OP_0_ONLY,
5, // 最大检测人脸数
FaceEngine.ASF_FACE_DETECT |
FaceEngine.ASF_AGE |
FaceEngine.ASF_GENDER |
FaceEngine.ASF_LIVENESS
);
if (initCode == ErrorInfo.MOK) {
CallbackUtil.invokeSuccess(callback, "引擎初始化成功");
} else {
CallbackUtil.invokeError(callback, "引擎初始化失败:" + initCode);
}
} else {
CallbackUtil.invokeError(callback, "引擎激活失败:" + activeCode);
}
}
/**
* 人脸检测
*/
@UniJSMethod(uiThread = false)
public void detectFace(JSONObject options, UniJSCallback callback) {
// 检查引擎状态
if (!isEngineActive || faceEngine == null) {
CallbackUtil.invokeError(callback, "引擎未初始化");
return;
}
String imagePath = options.getString("imagePath");
// ... 检测逻辑 ...
}
/**
* 释放引擎
*/
@UniJSMethod(uiThread = false)
public void releaseEngine(UniJSCallback callback) {
if (faceEngine != null) {
faceEngine.unInit();
faceEngine = null;
isEngineActive = false;
}
CallbackUtil.invokeSuccess(callback, "引擎已释放");
}
@Override
public void onActivityDestroy() {
super.onActivityDestroy();
if (faceEngine != null) {
faceEngine.unInit();
faceEngine = null;
}
}
}
3.4 配置 AppKey
AndroidManifest.xml 中配置:
xml
<manifest ...>
<application
android:name="io.dcloud.application.DCloudApplication"
...>
<!-- 应用入口Activity -->
<activity
android:name="io.dcloud.PandoraEntry"
...>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity
android:name="io.dcloud.PandoraEntryActivity"
.../>
<!-- dcloud_appkey - 离线打包key -->
<meta-data
android:name="dcloud_appkey"
android:value="your_app_key_here"/>
</application>
</manifest>
4. 测试
4.1 离线打包 Key
| 模式 | Key 来源 | 说明 |
|---|---|---|
| 真机运行 | HBuilderX 自动生成 | 临时有效,重启失效 |
| 自定义基座 | dcloud 控制台申请 | 用于自定义基座打包 |
| 正式打包 | dcloud 控制台申请 | 用于市场发布 |
申请离线打包 Key 流程:
- 登录 DCloud 开发者中心
- 进入「应用管理」→「离线打包Key」
- 填写 Android 包名和应用签名
- 复制 Key 到
AndroidManifest.xml
4.2离线打包

将离线打包产物复制到安卓项目
复制到app/src/main/assets
需要注意的是app/src/main/assets/data/dcloud_control.xml里面的appid要保持一致
5. 打包
体积需要控制下,不然插件在Uniapp打包的时候是需要收费的

5.1 APK 架构优化
只保留 arm64-v8a(推荐):
gradle
// build.gradle
android {
defaultConfig {
ndk {
abiFilters 'arm64-v8a'
}
}
packagingOptions {
jniLibs {
excludes += ['**/armeabi-v7a/**', '**/x86/**', '**/x86_64/**']
}
}
}
5.2 体积控制对比
| 包含架构 | APK 增量(相对单架构) | 说明 |
|---|---|---|
| 仅 arm64-v8a | +0% | ✅ 最小,推荐发布 |
| arm64 + armeabi-v7a | +15~25% | 兼容老设备 |
| arm64 + x86_64 | +20~30% | 模拟器支持 |
| 全部架构 | +50~80% | ❌ 不推荐 |
5.3 签名配置
app/build.gradle:
gradle
android {
signingConfigs {
config {
keyAlias 'your_alias'
keyPassword 'your_password'
storeFile file('your_keystore.jks')
storePassword 'your_password'
v1SigningEnabled true
v2SigningEnabled true
}
}
buildTypes {
release {
signingConfig signingConfigs.config
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
5.4 打包命令
bash
# 清理并构建
./gradlew clean assembleRelease
# 或使用 Android Studio
# Build → Generate Signed Bundle / APK → 选择 APK → 选择 release
5.5 生成的 AAR 文件位置
kotlin
uniplugin_arcface/build/outputs/aar/uniplugin_arcface-release.aar
6. uniapp市场规范
6.1 插件发布要求
| 要求项 | 说明 |
|---|---|
| 包名规范 | uniplugin_ 前缀 |
| 代码规范 | 无警告编译通过 |
| 文档要求 | 必须包含 README.md |
| 版本号 | 遵循语义化版本 |
6.2 插件目录结构
按照 UniApp 插件市场规范,创建以下目录结构:
arcface-plugin/
├── package.json # 插件配置文件
├── README.md # 使用说明文档
└── android/ # Android 平台文件
├── package.json # Android 平台配置
└── arcface-plugin.aar # 插件 AAR 包
6.3 package.json 配置
json
{
"name": "虹软人脸识别插件",
"id": "arcface-plugin",
"version": "1.0.0",
"description": "基于虹软SDK的人脸识别UniApp原生插件,支持人脸检测、年龄识别、性别识别、活体检测等功能",
"_dp_type": "nativeplugin",
"_dp_nativeplugin": {
"android": {
"plugins": [
{
"type": "module",
"name": "ArcFace",
"class": "io.dcloud.uniplugin.arcface.ArcFaceModule"
}
],
"integrateType": "aar",
"dependencies": [
"androidx.appcompat:appcompat:1.1.0",
"androidx.recyclerview:recyclerview:1.1.0",
"com.alibaba:fastjson:1.2.83"
],
"permissions": [
"android.permission.CAMERA",
"android.permission.READ_EXTERNAL_STORAGE",
"android.permission.WRITE_EXTERNAL_STORAGE",
"android.permission.INTERNET"
],
"abis": ["arm64-v8a"]
}
}
}
字段说明:
| 字段 | 说明 |
|---|---|
| name | 插件显示名称 |
| id | 插件唯一标识 |
| version | 插件版本号 |
| description | 插件描述 |
| _dp_type | 插件类型,固定为 nativeplugin |
| plugins | 插件功能列表 |
| integrateType | 集成方式,aar 表示 AAR 包集成 |
| dependencies | Maven 依赖列表 |
| permissions | 需要的权限列表 |
| abis | 支持的 CPU 架构 |
7. uniapp引入使用
7.1 自定义基座(自定义调试基座)

步骤:
- 在 HBuilderX 中打开 uniapp 项目
- 点击「运行」→「运行到手机或模拟器」→「制作自定义基座」
- 等待基座打包完成
- 使用自定义基座运行项目
7.2 插件引入
在 pages.json 中配置页面:
json
{
"pages": [
{
"path": "pages/arcface/arcface",
"style": {
"navigationBarTitleText": "虹软人脸识别"
}
}
]
}
7.3 调用插件功能
7.3.1 离线插件引入
将插件目录复制到 UniApp 项目的 nativeplugins 目录:
uniappDemo/unipluginDemo/
└── nativeplugins/
└── arcface-plugin/
├── package.json
├── README.md
└── android/
├── package.json
└── arcface-plugin.aar

在manifest.json设置离线插件

javascript
// 获取原生插件
const ArcFaceModule = uni.requireNativePlugin('ArcFace');
7.3.2 调用方法示例
完整 Vue 页面示例:
vue
<template>
<view class="container">
<!-- 版本信息 -->
<button type="primary" @click="getVersion">获取SDK版本</button>
<!-- 初始化引擎 -->
<button type="primary" @click="initEngine">初始化引擎</button>
<!-- 选择图片 -->
<button @click="chooseImage">选择图片</button>
<image v-if="imagePath" :src="imagePath" mode="aspectFit" />
<!-- 人脸检测 -->
<button type="primary" @click="detectFace" :disabled="!imagePath">
开始检测
</button>
<!-- 结果展示 -->
<view v-if="faceResults.length > 0">
<text>检测到 {{ faceResults.length }} 张人脸</text>
<view v-for="(face, index) in faceResults" :key="index">
<text>年龄:{{ face.age }}</text>
<text>性别:{{ face.genderText }}</text>
<text>活体:{{ face.livenessText }}</text>
</view>
</view>
<!-- 释放引擎 -->
<button type="warn" @click="releaseEngine">释放引擎</button>
</view>
</template>
<script>
export default {
data() {
return {
versionInfo: '',
engineStatus: '未初始化',
imagePath: '',
faceResults: []
}
},
onLoad() {
// 初始化时可以在这里做权限检查
this.requestPermissions()
},
methods: {
// ========== 权限申请 ==========
requestPermissions() {
// #ifdef APP-PLUS
const permissions = [
'android.permission.CAMERA',
'android.permission.READ_EXTERNAL_STORAGE'
];
plus.android.requestPermissions(
permissions,
(result) => {
console.log('权限申请结果:', result)
},
(error) => {
console.error('权限申请失败:', error)
}
)
// #endif
},
// ========== 获取SDK版本 ==========
getVersion() {
const ArcFaceModule = uni.requireNativePlugin('ArcFace')
ArcFaceModule.getVersion((result) => {
console.log('版本结果:', result)
if (result.code === 0) {
this.versionInfo = result.data.version
uni.showToast({ title: '版本:' + result.data.version, icon: 'none' })
} else {
uni.showToast({ title: result.message, icon: 'none' })
}
})
},
// ========== 初始化引擎 ==========
initEngine() {
uni.showLoading({ title: '初始化中...' })
const ArcFaceModule = uni.requireNativePlugin('ArcFace')
ArcFaceModule.initEngine({}, (result) => {
uni.hideLoading()
console.log('初始化结果:', result)
if (result.code === 0) {
this.engineStatus = '已初始化'
uni.showToast({ title: '初始化成功', icon: 'success' })
} else {
this.engineStatus = '初始化失败'
uni.showToast({ title: result.message, icon: 'none' })
}
})
},
// ========== 选择图片 ==========
chooseImage() {
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
this.imagePath = res.tempFilePaths[0]
this.faceResults = []
console.log('图片路径:', this.imagePath)
}
})
},
// ========== 人脸检测 ==========
detectFace() {
if (!this.imagePath) {
uni.showToast({ title: '请先选择图片', icon: 'none' })
return
}
uni.showLoading({ title: '检测中...' })
const ArcFaceModule = uni.requireNativePlugin('ArcFace')
// 处理路径(去掉 file:// 前缀)
let realPath = this.imagePath
if (realPath.startsWith('file://')) {
realPath = realPath.substring(7)
}
ArcFaceModule.detectFace({
imagePath: realPath
}, (result) => {
uni.hideLoading()
console.log('检测结果:', result)
if (result.code === 0) {
this.faceResults = result.data || []
const count = this.faceResults.length
uni.showToast({
title: count > 0 ? `检测到 ${count} 张人脸` : '未检测到人脸',
icon: 'success'
})
} else {
uni.showToast({ title: result.message, icon: 'none' })
}
})
},
// ========== 释放引擎 ==========
releaseEngine() {
const ArcFaceModule = uni.requireNativePlugin('ArcFace')
ArcFaceModule.releaseEngine((result) => {
console.log('释放结果:', result)
if (result.code === 0) {
this.engineStatus = '未初始化'
uni.showToast({ title: '引擎已释放', icon: 'success' })
}
})
}
},
onUnload() {
// 页面卸载时自动释放
this.releaseEngine()
}
}
</script>
7.4 通用插件调用模板
javascript
// 1. 获取插件
const Module = uni.requireNativePlugin('PluginName')
// 2. 调用异步方法(带回调)
Module.asyncMethod({ param1: 'value' }, (result) => {
if (result.code === 0) {
console.log('成功:', result.data)
} else {
console.error('失败:', result.message)
}
})
// 3. 调用同步方法
const result = Module.syncMethod()
console.log('同步结果:', result)