uni-app 自定义 Android 插件详解
文章目录
- uni-app 自定义 Android 插件详解
- 一、准备工作
- 二、创建插件目录结构
- 三、编写 `package.json`
- 四、编写 Android 原生代码 (Java)
- 1. 创建插件类 `MyAwesomePlugin.java`
- 2. `AndroidManifest.xml` (可选)
- 3. `build.gradle` (可选)
- 五、编写 JS 接口 (`index.js`)
- 六、在 uni-app 项目中使用插件
- 七、关键概念与最佳实践
- 八、总结
uni-app 自定义 Android 插件详解
在 uni-app 开发中,当内置 API 无法满足需求时(如调用特定硬件、集成第三方 SDK),就需要开发 自定义 Android 原生插件。这允许你使用 Java/Kotlin 编写原生代码,并通过 JavaScript 接口在 uni-app 中调用。
一、准备工作
- 开发环境:
- HBuilderX (推荐使用最新版)
- Android Studio
- JDK
- Android SDK
- 项目结构:
uni-app
项目根目录nativeplugins/
目录 (手动创建):存放所有自定义原生插件。
二、创建插件目录结构
在 nativeplugins/
下创建插件文件夹,结构如下:
nativeplugins/
└── my-awesome-plugin/ # 插件根目录 (命名规范: 小写字母、数字、下划线)
├── android/ # Android 原生代码
│ ├── src/
│ │ └── main/
│ │ ├── java/ # Java/Kotlin 源码
│ │ │ └── com/
│ │ │ └── dcloud/
│ │ │ └── plugin/
│ │ │ └── MyAwesomePlugin.java # 核心插件类
│ │ ├── AndroidManifest.xml # 插件清单文件
│ │ └── assets/ # 资源文件 (可选)
│ └── build.gradle # 插件构建脚本
├── package.json # 插件描述文件 (必须)
└── index.js # JS 调用接口 (可选,推荐)
三、编写 package.json
这是插件的核心描述文件,uni-app 通过它识别插件。
{"id": "my-awesome-plugin", // 插件唯一标识 (与目录名一致)"displayName": "我的插件", // 插件名称 (HBuilderX 中显示)"version": "1.0.0", // 版本号"description": "一个演示自定义插件的示例", // 描述"platforms": {"Android": ">=3.0.0" // 支持的平台和最低 uni-app 版本},"engines": {"HBuilderX": ">=3.0.0" // 最低 HBuilderX 版本},"permissions": ["Camera", // 需要的权限 (会自动合并到主应用)"Storage"],"hooks": [], // 钩子 (较少用)"nativePlugins": [{"type": "module", // 类型: module (功能模块)"name": "MyAwesomePlugin", // 模块名 (JS 中调用的名称)"android": {"plugins": [{"type": "service", // 插件类型: service (服务类)"name": "MyAwesomePlugin", // Java 类名 (不含包名)"class": "com.dcloud.plugin.MyAwesomePlugin" // 完整类名}]}}]
}
四、编写 Android 原生代码 (Java)
1. 创建插件类 MyAwesomePlugin.java
该类必须继承 io.dcloud.feature.uniapp.common.UniModule
。
package com.dcloud.plugin;import android.util.Log;
import android.widget.Toast;
import io.dcloud.feature.uniapp.annotation.UniJSMethod; // 注解: 标记 JS 可调用方法
import io.dcloud.feature.uniapp.common.UniModule;
import io.dcloud.feature.uniapp.ui.view.WebView;public class MyAwesomePlugin extends UniModule {private static final String TAG = "MyAwesomePlugin";// JS 调用的方法必须用 @UniJSMethod 注解// mode = UniJSMethod.THREAD_UI: 在 UI 线程执行 (用于更新 UI)// mode = UniJSMethod.THREAD_MAIN: 在主线程执行 (默认)@UniJSMethod(uiThread = true)public void showToast(String message, int duration, UniCallback callback) {Log.d(TAG, "收到 JS 消息: " + message);// 在 UI 线程执行 ToastToast.makeText(mWXSDKInstance.getContext(), message, duration == 1 ? Toast.LENGTH_SHORT : Toast.LENGTH_LONG).show();// 调用 callback 通知 JS 任务完成 (可选)if (callback != null) {// 返回给 JS 的数据UniMPJSResponse response = new UniMPJSResponse();response.setData("Toast 显示成功: " + message);response.setSuccess(true);callback.invoke(response);}}@UniJSMethodpublic void getDeviceInfo(UniCallback callback) {try {JSONObject deviceInfo = new JSONObject();deviceInfo.put("model", Build.MODEL);deviceInfo.put("brand", Build.BRAND);deviceInfo.put("version", Build.VERSION.RELEASE);UniMPJSResponse response = new UniMPJSResponse();response.setData(deviceInfo);response.setSuccess(true);callback.invoke(response);} catch (Exception e) {UniMPJSResponse response = new UniMPJSResponse();response.setSuccess(false);response.setMsg(e.getMessage());callback.invoke(response);}}// UniModule 提供的上下文// mWXSDKInstance.getContext() 获取 Context
}
2. AndroidManifest.xml
(可选)
如果插件需要声明 Activity、Service 或特殊权限,需在此文件中定义。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"><!-- 插件所需的权限 (如果 package.json 中已声明,可省略) --><!-- <uses-permission android:name="android.permission.CAMERA"/> --><!-- 如果需要启动 Activity --><!-- <activity android:name=".MyPluginActivity" /> --></manifest>
3. build.gradle
(可选)
如果插件依赖第三方库,则需要此文件。
dependencies {implementation 'com.squareup.okhttp3:okhttp:4.9.3'// 添加其他依赖...
}
五、编写 JS 接口 (index.js
)
提供一个更友好的 JavaScript 接口给开发者。
// nativeplugins/my-awesome-plugin/index.js// 导出方法
export function showToast(message, duration = 0) {// uni.requireNativePlugin 返回原生模块实例const MyAwesomePlugin = uni.requireNativePlugin('MyAwesomePlugin');return new Promise((resolve, reject) => {MyAwesomePlugin.showToast(message, duration, (result) => {if (result.success) {resolve(result.data);} else {reject(new Error(result.msg || '调用失败'));}});});
}export function getDeviceInfo() {const MyAwesomePlugin = uni.requireNativePlugin('MyAwesomePlugin');return new Promise((resolve, reject) => {MyAwesomePlugin.getDeviceInfo((result) => {if (result.success) {resolve(result.data);} else {reject(new Error(result.msg));}});});
}
六、在 uni-app 项目中使用插件
- HBuilderX 识别: 重启 HBuilderX,插件会自动被识别。
- 引用 JS 接口:
<template><view><button @click="handleClick">调用原生插件</button></view>
</template><script>
// 引入封装好的 JS 接口
import { showToast, getDeviceInfo } from '@/nativeplugins/my-awesome-plugin/index.js'export default {methods: {async handleClick() {try {// 调用 showToastawait showToast('Hello from Native!', 1);// 调用 getDeviceInfoconst info = await getDeviceInfo();console.log('设备信息:', info);} catch (error) {console.error('插件调用失败:', error);}}}
}
</script>
七、关键概念与最佳实践
-
@UniJSMethod
注解:- 标记 Java 方法可被 JS 调用。
uiThread = true
: 方法在 UI 线程执行,用于更新 UI (如Toast
,Dialog
)。callback
: JS 调用时传入的回调函数,在原生方法执行完毕后调用它返回结果。
-
线程安全:
- 默认在主线程执行。
- 耗时操作(网络、数据库)必须在子线程中进行,避免阻塞。
- 更新 UI 必须回到 UI 线程 (
uiThread = true
或使用runOnUiThread
)。
-
权限处理:
- 在
package.json
的permissions
中声明。 - Android 6.0+ 需要运行时动态申请权限。可在插件中使用
mWXSDKInstance.getActivity()
获取Activity
实例来申请。
- 在
-
调试:
- Java 代码: 使用 Android Studio 打开
unpackage
目录下的原生工程进行调试。 - 日志: 使用
Log.d()
输出日志,在 Android Studio 的 Logcat 中查看。 - JS 代码: 使用
console.log
。
- Java 代码: 使用 Android Studio 打开
-
打包:
- 在 HBuilderX 中正常打包 App。
- 插件代码会被自动集成到原生工程中。
-
错误处理:
- 原生代码中捕获异常,通过
callback
返回错误信息给 JS。 - JS 端使用
try-catch
或.catch()
处理 Promise 错误。
- 原生代码中捕获异常,通过
八、总结
开发 uni-app 自定义 Android 插件的核心流程:
- 创建结构:
nativeplugins/your-plugin/
目录。 - 编写
package.json
: 描述插件元信息。 - 编写 Java 类: 继承
UniModule
,用@UniJSMethod
标记方法。 - (可选) 编写
index.js
: 提供友好的 JS API。 - 在项目中使用: 通过
uni.requireNativePlugin
或导入index.js
调用。
自定义插件是扩展 uni-app 能力的关键,掌握它能让你的跨平台应用实现几乎任何原生功能。