UniApp 原生插件开发完整指南

UniApp 原生插件开发完整指南

目录

  1. 准备工作
  2. 新增功能模块
  3. 功能开发
  4. 测试
  5. 打包
  6. uniapp市场规范
  7. 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 流程:

  1. 登录 DCloud 开发者中心
  2. 进入「应用管理」→「离线打包Key」
  3. 填写 Android 包名和应用签名
  4. 复制 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 自定义基座(自定义调试基座)

步骤:

  1. 在 HBuilderX 中打开 uniapp 项目
  2. 点击「运行」→「运行到手机或模拟器」→「制作自定义基座」
  3. 等待基座打包完成
  4. 使用自定义基座运行项目

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)

文档出处

相关推荐
a_Ichuan1 小时前
在HBuilderX创建的uniapp项目中使用unocss
前端·uni-app
web前端神器2 小时前
记录uniapp小程序的报错
小程序·uni-app·apache
yqcoder4 小时前
uni-app 之 网络请求
网络·uni-app
克里斯蒂亚诺更新4 小时前
uniapp适配H5和Android-apk实现获取当前位置经纬度并调用接口
android·前端·uni-app
2501_9160088916 小时前
深入解析iOS应用启动性能优化策略与实践
android·ios·性能优化·小程序·uni-app·cocoa·iphone
巴巴博一1 天前
uni-app 踩坑实录:实现“可拖拽全局悬浮按钮”时的 movable-view 坐标失效与 Flex 布局视错觉
vue.js·uni-app
阿奇__1 天前
h5微信授权code失效排查过程及解决记录
微信·uni-app
天籁晴空1 天前
微信小程序 静默登录 + 授权登录 双模式配合的设计方案
前端·微信小程序·uni-app
爱怪笑的小杰杰2 天前
uni-app Vue3 国际化最佳实践:告别应用重启,优雅实现多语言切换
前端·vue.js·uni-app