当前位置: 首页 > news >正文

标准的JNI (Java Native Interface) 加载函数 JNI_OnLoad

1.JNI_OnLoad

在 Android Native 开发中,JNI_OnLoad 是动态注册本地方法的标准入口点。以下是一个标准实现示例及其说明:


JNI_OnLoad 标准实现

#include <jni.h>
#include <string>// 声明本地方法对应的 C/C++ 函数
jint native_add(JNIEnv* env, jobject thiz, jint a, jint b) {return a + b;
}// 定义 JNINativeMethod 结构体数组
static JNINativeMethod gMethods[] = {// Java方法名 | 方法签名 | 本地函数指针{"add", "(II)I", (void*)native_add},
};// 缓存 JavaVM 实例(用于后续获取 JNIEnv)
JavaVM* gJavaVM = nullptr;JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {JNIEnv* env = nullptr;jint result = -1;// 1. 获取 JNIEnvif (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {return JNI_ERR;}// 2. 缓存 JavaVM 实例gJavaVM = vm;// 3. 找到目标 Java 类const char* className = "com/example/MyJniClass";jclass clazz = env->FindClass(className);if (clazz == nullptr) {return JNI_ERR;}// 4. 注册本地方法if (env->RegisterNatives(clazz, gMethods, sizeof(gMethods)/sizeof(gMethods[0])) < 0) {return JNI_ERR;}// 5. 返回使用的 JNI 版本(必须与获取 JNIEnv 时指定的版本一致)return JNI_VERSION_1_6;
}

关键步骤说明

  1. 获取 JNIEnv
    通过 JavaVM::GetEnv 获取 JNIEnv 指针,需指定 JNI 版本(通常为 JNI_VERSION_1_6)。

  2. 缓存 JavaVM
    JavaVM 实例保存到全局变量中,以便在其他线程中通过 AttachCurrentThread 获取 JNIEnv

  3. 查找 Java 类
    使用 FindClass 找到需要注册本地方法的 Java 类(需全限定类名,如 com/example/MyJniClass)。

  4. 注册本地方法
    调用 RegisterNativesJNINativeMethod 数组中的方法动态注册到目标类。每个 JNINativeMethod 包含:

    • Java 方法名:与 Java 代码中的 native 方法名一致。
    • 方法签名:描述方法的参数和返回值类型(可通过 javap -s <ClassName> 生成)。
    • 本地函数指针:C/C++ 实现的函数地址。
  5. 返回 JNI 版本
    需返回与 GetEnv 中指定的版本一致的 JNI 版本号(如 JNI_VERSION_1_6)。


注意事项

  1. 方法签名
    必须与 Java 方法的参数和返回值类型完全匹配。例如:

    • (II)I 表示接收两个 int 参数并返回 int
    • (Ljava/lang/String;)V 表示接收一个 String 参数且无返回值。
  2. 避免名称改编(C++)
    如果使用 C++,需通过 extern "C"#pragma GCC visibility 确保函数名称不被编译器改编:

    extern "C" JNIEXPORT jint JNICALL Java_com_example_MyJniClass_add(JNIEnv*, jobject, jint, jint);
    
  3. 错误处理
    检查 FindClassRegisterNatives 的返回值,避免因注册失败导致崩溃。

  4. 线程安全
    通过缓存的 JavaVM,可在其他线程中安全获取 JNIEnv

    JNIEnv* env;
    gJavaVM->AttachCurrentThread(&env, nullptr);
    

优势(动态注册 vs 静态注册)

  • 更高效:避免运行时通过方法名动态查找本地函数。
  • 更灵活:无需遵循 Java_<Package>_<Class>_<Method> 的命名格式。
  • 可控性:可在 JNI_OnLoad 中集中管理所有注册逻辑。

通过以上实现,可确保本地方法在 JNI 库加载时正确注册,提升代码可维护性和性能。

2.调用JNI_OnLoad

在 Android 开发中,JNI_OnLoad 不是由开发者直接调用的,而是由 Java 虚拟机(JVM)在加载动态库(.so 文件)时自动触发的。以下是它的调用机制和触发方式:


1. JNI_OnLoad 的调用机制

当你在 Java 代码中加载一个 JNI 动态库(.so 文件)时,系统会自动检查该库是否实现了 JNI_OnLoad 函数。如果存在,JVM 会在库加载后立即调用它。

触发 JNI_OnLoad 的方式

// Java 代码(例如 MainActivity.java)
static {System.loadLibrary("myjni"); // 加载 libmyjni.so
}
  • System.loadLibrary("myjni")
    会尝试加载 libmyjni.so(在 Android 中会自动添加 lib 前缀和 .so 后缀)。
  • JVM 检查 JNI_OnLoad
    如果 libmyjni.so 实现了 JNI_OnLoad,JVM 会在加载后立即调用它。

2. JNI_OnLoad 的调用流程

  1. Java 代码调用 System.loadLibrary("myjni")
  2. Android Runtime(ART/Dalvik)加载 libmyjni.so
  3. JVM 检查是否实现了 JNI_OnLoad
    • 如果存在,调用 JNI_OnLoad(JavaVM* vm, void* reserved)
    • 如果不存在,JNI 会使用默认的静态注册方式(基于 JNIEXPORT 函数名匹配)。
  4. JNI_OnLoad 执行动态注册
    • 注册 JNINativeMethod 数组中的本地方法。
    • 返回 JNI 版本(如 JNI_VERSION_1_6)。

3. 如何确保 JNI_OnLoad 被正确调用?

(1)确保 .so 文件正确编译

CMakeLists.txtAndroid.mk 中正确配置:

# CMakeLists.txt
add_library(myjni SHARED native-lib.cpp)

# Android.mk
LOCAL_MODULE := myjni
LOCAL_SRC_FILES := native-lib.cpp

(2)确保 Java 代码正确加载库

// 在静态代码块中加载库(避免重复加载)
static {System.loadLibrary("myjni");
}

(3)检查 JNI_OnLoad 是否被调用

可以在 JNI_OnLoad 中添加日志:

#include <android/log.h>
#define LOG_TAG "JNI_DEBUG"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {LOGD("JNI_OnLoad called!"); // 检查是否执行// ... 其他注册逻辑return JNI_VERSION_1_6;
}

然后在 logcat 中查看日志:

adb logcat | grep "JNI_DEBUG"

4. 常见问题

(1)JNI_OnLoad 没有被调用?

  • 库未正确加载:检查 System.loadLibrary 是否成功执行(无 UnsatisfiedLinkError)。
  • .so 文件未正确编译:确保 JNI_OnLoad 被包含在最终 .so 中(可通过 nm -D libmyjni.so | grep JNI_OnLoad 检查)。
  • JNI 版本不匹配:确保 JNI_OnLoad 返回正确的版本(如 JNI_VERSION_1_6)。

(2)JNI_OnLoad 和静态注册的区别?

特性JNI_OnLoad(动态注册)静态注册(默认方式)
注册方式主动调用 RegisterNatives基于函数名匹配(如 Java_com_example_MyClass_method
性能更快(避免运行时查找)较慢(首次调用需解析)
灵活性可自定义注册逻辑必须遵循固定命名规则
适用场景推荐用于复杂项目简单 JNI 调用

5. 总结

  • JNI_OnLoad 由 JVM 自动调用,开发者只需实现它并在其中注册本地方法。
  • 触发方式:在 Java 中调用 System.loadLibrary("mylib")
  • 核心作用:动态注册 JNI 方法,提升性能并增强灵活性。
  • 调试技巧:通过 __android_log_print 打印日志,确保 JNI_OnLoad 被正确执行。

如果你的 JNI_OnLoad 没有被调用,请检查库加载流程和编译配置!

相关文章:

  • VLC搭建本机的rtsp直播推流和拉流
  • 如何成为Agent工程师:学习路径、核心技能与职业规划
  • 深入剖析 MySQL 中用户授权机制及操作
  • 指针(3)
  • 【上位机——MFC】MFC入门
  • day 22 作业
  • 实战指南:封装Faster-Whisper为FastAPI接口并实现高并发处理-附整合包
  • SAP PO开发-端到端配置
  • 2.1 基于委托的异步编程方法
  • SSRF学习
  • Spring 01
  • 9、Hooks:现代魔法咒语集——React 19 核心Hooks
  • 数字系统与编码
  • 计算机组成原理笔记(十六)——4.1基本算术运算的实现
  • Java Streams 使用教程
  • 即梦AI与可灵AI视频生成功能对比分分析
  • AI与思维模型【70】——遗忘曲线
  • 从外网访问局域网服务器的方法+Linux文件和命令
  • App-Controller - 通过自然语言操控应用程序的智能框架
  • tigase源码学习杂记-组件化设计
  • 贵州赤水“整改复耕”:为何竹林砍了,地却荒了?
  • 消息人士称哈马斯愿与以色列达成长期停火
  • 复旦大学空间互联网研究院成立,将聚焦卫星互联网等领域
  • 在历史上遭到起诉的杀人动物记录中,为什么猪如此普遍?
  • 王毅、董军将主持召开中印尼外长防长“2+2”对话机制首次部长级会议
  • 马上评丨黄仁勋到访中国,一个重要节点和一句大实话