LOADING...

加载过慢请开启缓存(浏览器默认开启)

loading

UniAPP_Android_Javascript调用so文件

一、概述#

1. 整体流程#

1
2
3
4
5
6
7
8
9
10
graph LR
Start(开始)
--> C[C代码]
C --> |编译器|E[编译为.so]
E --> |Java|S[JNI调用]
S --> |Gradle|R[打包为aar原生插件]
R --> |UniApp|H|[引入插件]
H --> |HBuilder|J|[准备.so]
X --> |JS|D|[JS调用]
D --> End(结束)

2. 环境准备#

  1. 按需安装Android Studio、模拟器、NDK 等环境,确保 adb devices 可以查看到手机设备/模拟器
  2. 安装NodeJS、HBuilder,安卓手机一台,打开开发者模式,与开发环境连接(USB或者无线调试)

二、C编译生成so文件#

1. 安装 Android NDK#

通过 Android Studio 安装:
打开 Android Studio -> Preferences -> Android SDK -> SDK Tools。
勾选 NDK (Side by side) 和 CMake,点击 Apply 安装。

2. 配置 NDK 环境变量#

找到 NDK 的安装路径:
默认路径通常为:/Users/your_user/Library/Android/sdk/ndk/{version}
在终端中临时设置环境变量(或永久写入 ~/.bash_profile):
export NDK_HOME=/path/to/your/ndk # 有{version}
export PATH=$PATH:$NDK_HOME
source ~/.zshrc 或者 source ~/.bash_profile

3. 编写编译脚本(NDK Build)#

已完成不需要修改

4. 执行编译#

打开终端,进入项目根目录(YourProject/)
cd /path/to/YourProject
运行 ndk-build 命令:
$ANDROID_NDK_HOME/ndk-build
编译成功后,生成的 .so 文件会出现在:
YourProject/libs/arm64-v8a/md5.so

三、UniApp调用Java#

  1. 下载 UniApp原生开发SDK,下载地址见App离线SDK下载。
  2. 解压后,打开示例工程UniPlugin-Hello-AS来进行插件开发
  3. 打开AndroidStudio,导入上述工程 File->Import Project,选择 UniPlugin-Hello-AS
  4. 创建 Module: AndroidStudio -> File -> New Module -> Android Library -> 输入模块名:smandroid
  5. 配置 Module Gradle: 打开 build.gradle 文件,添加如下代码:
1
2
3
4
5
6
7
8
9
10
11
12
repositories {
flatDir {
dirs 'libs'
}
}
dependencies {
compileOnly 'androidx.recyclerview:recyclerview:1.0.0'
compileOnly 'androidx.legacy:legacy-support-v4:1.0.0'
compileOnly 'androidx.appcompat:appcompat:1.0.0'
compileOnly 'com.alibaba:fastjson:1.1.46.android'
compileOnly fileTree(include: ['uniapp-v8-release.aar'], dir: '../app/libs')
}
  1. 点击右上角 gradle Sync Now 进行同步, 或者 File -> Sync Project With Gradle Files
  2. 打开Module根目录下的proguard-rules.pro文件,在最后一行末尾加入以下代码
    -keep public class * extends io.dcloud.feature.uniapp.common.UniModule{*;}
  3. 在Module的项目文件夹下创建Module类MySMLib, 使其扩展 UniModule
    public class MySMLib extends UniModule
  4. 完善功能代码
  • 扩展方法必须加上@UniJSMethod (uiThread = false or true) 注解。 UniApp 会根据注解来判断当前方法是否要运行在 UI 线程,和当前方法是否是扩展方法。
  • UniApp是根据反射来进行调用 Module 扩展方法,所以Module中的扩展方法必须是 public 类型。
  • C 代码当中的接口函数加 native 修饰符,例如: public static native int GenerateRandom(int length,byte[] randombuff);
  • 简单返回类型可以直接返回,或者参考官方文档使用 UniJSCallback 返回。
  • 使用callback 返回时,注意引入的JSON包用 alibaba.fastjson
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.ss.test;
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 MySMLib extends UniModule {
@UniJSMethod(uiThread = true)
public void sayHi (String name, UniJSCallback callback) {
if (callback != null) {
JSONObject data = new JSONObject();
data.put("re", "Hi " + name);
callback.invoke(data);
}
}
}
  1. 调试插件,注意,是在AndroidStudio中,进行原生插件调试
  • 打开UniApp新建一个项目,打开manifest.json文件,获取appid
  • 在uniapp开发者后台,找到对应的appid,创建证书
  • 查看证书详情,保存证书信息、密码、SHA1、Alias等
  • 下载证书,放在原生插件项目的app目录下( Android Studio -> UniPlugin-Hello-AS -> app )
  • 在 Android Studio 插件项目app目录下的build.gradle文件中,将信息依次填入文件中的signingConfigs.config配置项,然后点击右上角同步完成配置
  • 注册插件 Android Studio 插件项目的app\src\main\assets目录下打开dcloud_uniplugins.json,在nativePlugins配置项中添加一项插件配置,type必须为module
    android配置图
  • Android Studio -> app -> build.gradle 中,引入插件
    implement project(‘MySMLib’)
  1. UniApp 集成调试
  • 使用uni.requireNativePlugin(‘插件名’)方法调用插件
  • 调用插件中的sayHi方法,利用回调函数获取返回值
  • 完成相关代码编写后,需要生成uniapp本地打包资源 HBuilder->发行->生成本地App资源
  • 将生成的打包资源复制到 Android Studio 插件项目的app\src\main\assets\apps目录下
  • Android Studio 在插件项目app\src\main\assets\data目录下的dcloud_control.xml文件中配置uniapp的appid
  • 在Android Studio中运行项目, 注意选择为 app 点击 绿色三角形运行

四、Java调用.so#

  1. 使用JNI调用方法,调用.so文件
1
2
3
4
5
6
public class MySMLib extends UniModule{
private static final String LIB_NAME = "whatever";
static {
System.loadLibrary(LIB_NAME);
}
}
  1. 注意,如果需要.so 动态替换,详见下文第六部分

五、UniApp调用.so#

  1. Android Studio 点击插件项目,点击右上角 Gradle,Tasks->Other,双击 assembleRelease
  2. 注意,如果other下没有 assembleRelease,请点击 Android Studio -> Settings -> Experimental -> 选中 Configure all Gradle tasks during xxxx
  3. 编译成功后,就可以在插件目录的build/outputs/arr目录下找到编译好的插件
  4. 把插件复制到 实际 UniApp 项目中。
  5. HBuilder -> 项目 -> 创建 nativeplugins 目录,把插件复制到 nativeplugins 目录下,注意目录层次:
    [Project Name] /
    • nativeplugins/
      • smandroid/
        • android/
          • MySMLib-release.aar
  1. 在smandroid下创建package.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"name": "MySMLib",
"id": "MySMLib",
"version": "0.0.2",
"description": "xx插件",
"_dp_type":"nativeplugin",
"_dp_nativeplugin": {
"android": {
"plugins": [
{"type": "module",
"name": "test",
"class": "com.example.smandroid.MySMLib"}
],
"integrateType": "aar",
"parameters": {},
"dependencies": []
}
}
}
  1. 在uniapp项目的manifest.json文件中添加配置好的插件
  2. 打包自定义基座,输入打包信息、证书名、证书文件、密码等,提交 传统打包
  3. 选择自定义基座,运行 UniApp 项目

六、aar打包与.so动态替换#

本方案实现了SO文件的动态更新能力,同时保证了基础功能的可用性。建议在实际业务中结合CDN版本管理和灰度发布策略,确保更新过程的稳定可靠。

1. 动态更新SO操作手册#

环境准备#

开发工具
- Android Studio (≥ Arctic Fox)
- HBuilderX (≥ 3.6.0)
- JDK 11

目录结构规划
假设目录结构如下

1
2
3
4
5
6
7
8
9
10
UniAppProject/
├── nativeplugins/
│ └── smandroid/
│ ├── android/
│ │ ├── smandroid.aar # 主模块
│ │ └── libs/ # 初始SO库
│ │ ├── arm64-v8a/
│ │ └── x86_64/
│ └── package.json
└── ... # UniApp主项目

Android模块开发#

创建Android Library模块#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// build.gradle (Module)
plugins {
id 'com.android.library'
}

android {
compileSdk 34
namespace 'com.example.smandroid'

// 关键配置:不打包SO到AAR
sourceSets {
main {
jniLibs.srcDirs = [] // 清空默认路径
}
}

packagingOptions {
exclude '**/*.so' // 全局排除
}
}

dependencies {
compileOnly fileTree(dir: '../app/libs', include: ['uniapp-v8-release.aar'])
}

实现动态加载逻辑#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class DynamicLoader extends UniModule {
private static final String LIB_DIR_NAME = "dynamic_libs";
private static final String VERSION_FILE = "lib_versions.json";

// 初始化加载
static {
loadNativeLibs(null); // 首次加载
}

// 统一加载入口
public static void loadNativeLibs(Context context) {
try {
// 优先尝试系统路径
System.loadLibrary("SMAPI");
System.loadLibrary("smJNIAPI");
} catch (UnsatisfiedLinkError e) {
// 降级到动态加载
if (context != null) {
File libDir = prepareLibDir(context);
loadFromDir(libDir);
}
}
}

// 准备动态库目录
private static File prepareLibDir(Context context) {
File libDir = new File(context.getFilesDir(), LIB_DIR_NAME);
if (!libDir.exists()) {
libDir.mkdirs();
libDir.setExecutable(true, false);
libDir.setReadable(true, false);
}
return libDir;
}

// 从指定目录加载
private static void loadFromDir(File libDir) {
System.load(new File(libDir, "libSMAPI.so").getAbsolutePath());
System.load(new File(libDir, "libsmJNIAPI.so").getAbsolutePath());
}
}

UniApp集成配置#

插件目录结构#

1
2
3
4
5
6
7
8
9
10
11
smandroid/
├── android/
│ ├── smandroid.aar
│ └── libs/ # 基础版本SO
│ ├── arm64-v8a/
│ │ ├── libSMAPI.so
│ │ └── libsmJNIAPI.so
│ └── x86_64/
│ ├── libSMAPI.so
│ └── libsmJNIAPI.so
└── package.json

package.json配置#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"id": "smandroid",
"name": "smandroid",
"version": "1.0.0",
"_dp_type": "nativeplugin",
"_dp_nativeplugin": {
"android": {
"plugins": [{
"type": "module",
"name": "smandroid",
"class": "com.example.smandroid.DynamicLoader"
}],
"integrateType": "aar",
"libs": ["libs/*.so"], // 声明基础库
"permissions": [
"android.permission.INTERNET",
"android.permission.WRITE_EXTERNAL_STORAGE"
]
}
}
}

动态更新实现#

版本检测接口#

1
2
3
4
5
6
7
8
9
10
11
// uni-app侧
const checkUpdate = () => {
const plugin = uni.requireNativePlugin('smandroid');
plugin.checkVersion({
currentVersion: '1.0.0'
}, res => {
if (res.needUpdate) {
downloadLib(res.url);
}
});
}

文件下载与校验#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Android侧
@UniJSMethod
public void downloadLib(String url, UniJSCallback callback) {
new Thread(() -> {
try {
File targetDir = getLibDir();
File tempFile = downloadFile(url, targetDir);

if (verifyFile(tempFile)) {
replaceLib(tempFile);
callback.invoke("SUCCESS");
} else {
tempFile.delete();
callback.invoke("FILE_CORRUPTED");
}
} catch (Exception e) {
callback.invoke("DOWNLOAD_FAILED");
}
}).start();
}

private boolean verifyFile(File file) {
// SHA256校验示例
String expectedHash = "9f86d08...";
return HashUtils.sha256(file).equals(expectedHash);
}

安全替换机制#

1
2
3
4
5
6
7
8
9
10
11
12
13
private void replaceLib(File newLib) {
File libDir = getLibDir();
File destFile = new File(libDir, newLib.getName());

// 原子操作
File tempFile = new File(destFile.getAbsolutePath() + ".tmp");
copyFile(newLib, tempFile);

if (verifyFile(tempFile)) {
tempFile.renameTo(destFile);
System.load(destFile.getAbsolutePath());
}
}

调试与验证#

查看运行时加载路径#

1
adb shell cat /proc/[pid]/maps | grep .so

强制触发更新#

1
2
3
4
// 开发调试用
uni.$emit('forceUpdate', {
url: 'http://test.com/libs/v2/libSMAPI.so'
});

日志追踪配置#

1
2
3
4
5
// 在Application中初始化
public void onCreate() {
super.onCreate();
System.setProperty("jni.debug", "1"); // 启用JNI调试日志
}

七、注意事项#

  1. 版本兼容性

    • 保持SO文件与SDK版本ABI严格匹配
    • 使用Build.SUPPORTED_ABIS检测设备支持架构
  2. 安全策略

    • HTTPS传输动态库
    • 每次加载前进行签名校验
    • 保留旧版本回滚能力
  3. 性能优化

    • 差分更新(bsdiff算法)
    • 后台静默下载
    • 安装时机选择(应用切换到后台时)

八、参考资料#

  1. https://blog.csdn.net/xiao_qiang666/article/details/136495454
  2. https://www.imspm.com/dev/433773.html
  3. https://blog.csdn.net/qq_43548590/article/details/132333148
  4. https://ask.dcloud.net.cn/article/35414