2026/1/7 22:07:44
网站建设
项目流程
经典网站代码,沧州公司官网制作,aso优化师,搜外友链平台一、JNI 基础概念1.1 什么是 JNI#xff1f;JNI (Java Native Interface) 是 Java 提供的一套标准接口#xff0c;允许#xff1a;Java 代码调用 C/C 代码#xff08;Native 方法#xff09;C/C 代码调用 Java 代码#xff08;反向调用#xff09;┌──────────…一、JNI 基础概念1.1 什么是 JNIJNI (Java Native Interface)是 Java 提供的一套标准接口允许Java 代码调用 C/C 代码Native 方法C/C 代码调用 Java 代码反向调用┌─────────────────────────────────────┐ │ Java 层 │ │ Java 代码运行在 JVM/ART 中 │ └──────────────┬──────────────────────┘ │ │ JNI 接口 │ ┌──────────────┴──────────────────────┐ │ Native 层 │ │ C/C 代码直接运行在操作系统上 │ └─────────────────────────────────────┘1.2 为什么需要 JNI性能C/C 比 Java 快适合计算密集型任务硬件访问直接操作硬件、调用系统 API代码复用使用现有的 C/C 库安全核心算法用 C 实现不易被反编译二、JNI 的工作原理2.1 函数指针映射JNI 通过函数指针映射。Java 层声明 Native 方法// 文件MyClass.java public class MyClass { // 1. 声明 native 方法没有实现 public native String sayHello(String name); public native int add(int a, int b); // 2. 加载 Native 库 static { System.loadLibrary(mynative); // 加载 libmynative.so } }关键点native关键字表示这个方法在 C/C 中实现方法只有声明没有方法体必须加载对应的.so库文件C 层实现 Native 方法// 文件mynative.cpp #include jni.h #include string // JNI 方法命名规则Java_包名_类名_方法名 // 包名中的 . 替换为 _ // 例如com.example.MyClass.sayHello // 变成Java_com_example_MyClass_sayHello extern C JNIEXPORT jstring JNICALL Java_com_example_MyClass_sayHello( JNIEnv* env, // JNI 环境指针重要 jobject thiz, // Java 对象的引用this jstring name) { // Java 参数 // 1. 将 Java String 转换为 C string const char* nameStr env-GetStringUTFChars(name, nullptr); // 2. C 处理 std::string result Hello, std::string(nameStr) !; // 3. 释放 Java String env-ReleaseStringUTFChars(name, nameStr); // 4. 将 C string 转换为 Java String return env-NewStringUTF(result.c_str()); } extern C JNIEXPORT jint JNICALL Java_com_example_MyClass_add( JNIEnv* env, jobject thiz, jint a, jint b) { // 直接返回结果 return a b; }关键点extern C防止 C 名称修饰name manglingJNIEXPORT导出符号让 JVM 能找到JNICALL调用约定方法名必须遵循特定规则第一个参数总是JNIEnv*第二个参数是jobject实例方法或jclass静态方法2.2 JNI 连接过程详解步骤 1加载 Native 库static { System.loadLibrary(mynative); }这行代码会// Android 系统内部实现 // dalvik/vm/native/java_lang_Runtime.cpp void Dalvik_java_lang_Runtime_nativeLoad(const u4* args, JValue* pResult) { StringObject* fileNameObj (StringObject*) args[0]; // 1. 获取库文件路径 char* fileName dvmCreateCstrFromString(fileNameObj); // 例如/system/lib64/libmynative.so // 2. 使用 dlopen 加载动态库 void* handle dlopen(fileName, RTLD_LAZY); if (handle nullptr) { // 加载失败 RETURN_VOID(); } // 3. 查找 JNI_OnLoad 函数 typedef jint (*JNI_OnLoadFn)(JavaVM* vm, void* reserved); JNI_OnLoadFn JNI_OnLoad (JNI_OnLoadFn)dlsym(handle, JNI_OnLoad); // 4. 如果存在 JNI_OnLoad调用它 if (JNI_OnLoad ! nullptr) { jint version (*JNI_OnLoad)(gDvm.vmList, nullptr); } RETURN_VOID(); }dlopen 是什么Linux 系统调用用于加载动态链接库.so 文件返回库的句柄handle类似 Windows 的 LoadLibrary步骤 2方法注册两种方式方式 1静态注册按命名规则自动查找// JVM 会根据方法名自动查找 // Java_com_example_MyClass_sayHello // 当 Java 调用 sayHello 时 // 1. JVM 构造函数名Java_com_example_MyClass_sayHello // 2. 使用 dlsym 在 .so 中查找这个符号 void* funcPtr dlsym(handle, Java_com_example_MyClass_sayHello); // 3. 将函数指针保存到方法表中 // 4. 后续调用直接通过函数指针调用方式 2动态注册推荐Android 常用// mynative.cpp // 1. 定义实际的实现函数名字可以随意 static jstring native_sayHello(JNIEnv* env, jobject thiz, jstring name) { const char* nameStr env-GetStringUTFChars(name, nullptr); std::string result Hello, std::string(nameStr) !; env-ReleaseStringUTFChars(name, nameStr); return env-NewStringUTF(result.c_str()); } static jint native_add(JNIEnv* env, jobject thiz, jint a, jint b) { return a b; } // 2. 定义方法映射表 static JNINativeMethod gMethods[] { // Java 方法名 方法签名 C 函数指针 {sayHello, (Ljava/lang/String;)Ljava/lang/String;, (void*)native_sayHello}, {add, (II)I, (void*)native_add}, }; // 3. 在 JNI_OnLoad 中注册 JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; // 获取 JNI 环境 if (vm-GetEnv((void**)env, JNI_VERSION_1_6) ! JNI_OK) { return JNI_ERR; } // 查找 Java 类 jclass clazz env-FindClass(com/example/MyClass); if (clazz nullptr) { return JNI_ERR; } // 注册 Native 方法 if (env-RegisterNatives(clazz, gMethods, sizeof(gMethods)/sizeof(gMethods[0])) 0) { return JNI_ERR; } return JNI_VERSION_1_6; }动态注册的优势函数名可以随意命名不需要很长的名字注册更快不需要每次都查找符号Android Framework 都使用这种方式步骤 3调用过程Java 调用 native 方法 ↓ JVM/ART 查找方法表 ↓ 找到对应的 C 函数指针 ↓ 切换到 Native 栈 ↓ 执行 C 代码 ↓ 返回结果 ↓ 切换回 Java 栈 ↓ 继续执行 Java 代码2.3 JNI 方法签名什么是方法签名方法签名描述了方法的参数类型和返回类型// Java 方法 public String sayHello(String name) // JNI 签名 (Ljava/lang/String;)Ljava/lang/String; // ↑ 参数 ↑ 返回值签名规则Java 类型JNI 签名C/C 类型booleanZjbooleanbyteBjbytecharCjcharshortSjshortintIjintlongJjlongfloatFjfloatdoubleDjdoublevoidVvoidStringLjava/lang/String;jstringint[][IjintArrayObject[][Ljava/lang/Object;jobjectArray示例// Java 方法 public int add(int a, int b) // 签名(II)I public void setData(String name, int age, boolean isStudent) // 签名(Ljava/lang/String;IZ)V public String[] getNames() // 签名()[Ljava/lang/String;三、JNIEnv 详解3.1 什么是 JNIEnvJNIEnv是 JNI 环境指针提供了所有 JNI 函数struct JNIEnv { // 字符串操作 jstring NewStringUTF(const char* bytes); const char* GetStringUTFChars(jstring string, jboolean* isCopy); void ReleaseStringUTFChars(jstring string, const char* utf); // 对象操作 jobject NewObject(jclass clazz, jmethodID methodID, ...); void DeleteLocalRef(jobject obj); jobject NewGlobalRef(jobject obj); // 类操作 jclass FindClass(const char* name); jclass GetObjectClass(jobject obj); // 方法操作 jmethodID GetMethodID(jclass clazz, const char* name, const char* sig); jobject CallObjectMethod(jobject obj, jmethodID methodID, ...); jint CallIntMethod(jobject obj, jmethodID methodID, ...); // 字段操作 jfieldID GetFieldID(jclass clazz, const char* name, const char* sig); jint GetIntField(jobject obj, jfieldID fieldID); void SetIntField(jobject obj, jfieldID fieldID, jint value); // 数组操作 jintArray NewIntArray(jsize length); jint* GetIntArrayElements(jintArray array, jboolean* isCopy); void ReleaseIntArrayElements(jintArray array, jint* elems, jint mode); // 异常处理 jthrowable ExceptionOccurred(); void ExceptionClear(); void ThrowNew(jclass clazz, const char* message); // ... 还有很多其他函数 };重要JNIEnv 是线程相关的每个线程都有自己的 JNIEnv不能跨线程使用 JNIEnv不能缓存 JNIEnv 指针3.2 JNIEnv 使用示例示例 1字符串操作JNIEXPORT jstring JNICALL Java_com_example_MyClass_processString(JNIEnv* env, jobject thiz, jstring input) { // 1. Java String → C string const char* inputStr env-GetStringUTFChars(input, nullptr); // 2. 处理字符串 std::string result std::string(inputStr) processed; // 3. 释放 Java String重要防止内存泄漏 env-ReleaseStringUTFChars(input, inputStr); // 4. C string → Java String return env-NewStringUTF(result.c_str()); }示例 2调用 Java 方法// Java 类 public class Calculator { public int multiply(int a, int b) { return a * b; } }// C 调用 Java 方法 JNIEXPORT jint JNICALL Java_com_example_MyClass_callJavaMethod(JNIEnv* env, jobject thiz) { // 1. 查找 Java 类 jclass calcClass env-FindClass(com/example/Calculator); if (calcClass nullptr) { return -1; // 类未找到 } // 2. 获取构造方法 ID jmethodID constructor env-GetMethodID(calcClass, init, ()V); // 3. 创建 Java 对象 jobject calcObj env-NewObject(calcClass, constructor); // 4. 获取方法 ID jmethodID multiplyMethod env-GetMethodID(calcClass, multiply, (II)I); // 5. 调用 Java 方法 jint result env-CallIntMethod(calcObj, multiplyMethod, 10, 20); // 6. 释放本地引用 env-DeleteLocalRef(calcObj); env-DeleteLocalRef(calcClass); return result; // 返回 200 }示例 3访问 Java 字段// Java 类 public class Person { private String name; private int age; }// C 访问 Java 字段 JNIEXPORT void JNICALL Java_com_example_MyClass_modifyPerson(JNIEnv* env, jobject thiz, jobject person) { // 1. 获取类 jclass personClass env-GetObjectClass(person); // 2. 获取字段 ID jfieldID nameField env-GetFieldID(personClass, name, Ljava/lang/String;); jfieldID ageField env-GetFieldID(personClass, age, I); // 3. 读取字段值 jstring name (jstring)env-GetObjectField(person, nameField); jint age env-GetIntField(person, ageField); // 4. 修改字段值 jstring newName env-NewStringUTF(John Doe); env-SetObjectField(person, nameField, newName); env-SetIntField(person, ageField, age 1); // 5. 释放引用 env-DeleteLocalRef(newName); env-DeleteLocalRef(personClass); }示例 4数组操作JNIEXPORT jintArray JNICALL Java_com_example_MyClass_doubleArray(JNIEnv* env, jobject thiz, jintArray input) { // 1. 获取数组长度 jsize length env-GetArrayLength(input); // 2. 获取数组元素指针 jint* elements env-GetIntArrayElements(input, nullptr); // 3. 创建新数组 jintArray output env-NewIntArray(length); // 4. 处理数据 jint* outputElements env-GetIntArrayElements(output, nullptr); for (int i 0; i length; i) { outputElements[i] elements[i] * 2; } // 5. 释放数组重要 env-ReleaseIntArrayElements(input, elements, JNI_ABORT); // 不回写 env-ReleaseIntArrayElements(output, outputElements, 0); // 回写 return output; }四、什么是反射4.1 反射的定义反射 (Reflection)是指程序在运行时能够检查自身的结构类、方法、字段动态调用方法动态访问字段动态创建对象关键反射是在运行时而不是编译时4.2 Java 反射示例// 普通调用编译时确定 Person person new Person(); person.setName(Alice); String name person.getName(); // 反射调用运行时确定 try { // 1. 获取类对象 Class? clazz Class.forName(com.example.Person); // 2. 创建实例 Object person clazz.newInstance(); // 3. 获取方法 Method setNameMethod clazz.getMethod(setName, String.class); Method getNameMethod clazz.getMethod(getName); // 4. 调用方法 setNameMethod.invoke(person, Alice); String name (String)getNameMethod.invoke(person); System.out.println(name); // 输出Alice } catch (Exception e) { e.printStackTrace(); }4.3 反射的应用场景场景 1框架开发// Spring 框架使用反射创建 Bean Component public class UserService { Autowired private UserRepository userRepository; } // Spring 内部实现简化版 Class? clazz Class.forName(com.example.UserService); Object bean clazz.newInstance(); // 查找带 Autowired 注解的字段 for (Field field : clazz.getDeclaredFields()) { if (field.isAnnotationPresent(Autowired.class)) { // 注入依赖 Object dependency getBeanByType(field.getType()); field.setAccessible(true); field.set(bean, dependency); } }场景 2序列化/反序列化// Gson 使用反射将 JSON 转换为对象 String json {\name\:\Alice\,\age\:25}; Person person gson.fromJson(json, Person.class); // Gson 内部实现简化版 Class? clazz Person.class; Object obj clazz.newInstance(); JSONObject jsonObj new JSONObject(json); for (Field field : clazz.getDeclaredFields()) { String fieldName field.getName(); if (jsonObj.has(fieldName)) { field.setAccessible(true); field.set(obj, jsonObj.get(fieldName)); } }场景 3插件系统// 动态加载插件 String pluginClassName config.getPluginClass(); Class? pluginClass Class.forName(pluginClassName); Plugin plugin (Plugin)pluginClass.newInstance(); plugin.execute();4.4 反射 vs JNI特性反射JNI语言Java 调用 JavaJava 调用 C/C时机运行时动态编译时静态方法注册后性能较慢需要查找快直接函数指针用途动态调用 Java 代码调用 Native 代码类型安全运行时检查编译时检查重要区别JNI 不是反射JNI 通过函数指针直接调用不需要运行时查找JNI 可以使用反射在 C 中可以通过 JNIEnv 使用类似反射的功能4.5 JNI 中的反射式调用虽然 JNI 本身不是反射但可以实现类似反射的功能// 类似反射的 JNI 调用 JNIEXPORT void JNICALL Java_com_example_MyClass_reflectionLikeCall(JNIEnv* env, jobject thiz) { // 1. 动态查找类类似 Class.forName jclass clazz env-FindClass(com/example/Person); // 2. 动态查找方法类似 getMethod jmethodID method env-GetMethodID(clazz, setName, (Ljava/lang/String;)V); // 3. 动态调用方法类似 invoke jstring name env-NewStringUTF(Alice); env-CallVoidMethod(thiz, method, name); // 这看起来像反射但实际上是直接的函数指针调用 }五、完整示例Android 中的 JNI让我给你一个完整的 Android JNI 示例5.1 Java 层// MainActivity.java package com.example.jnidemo; public class MainActivity extends AppCompatActivity { // 1. 加载 Native 库 static { System.loadLibrary(native-lib); } // 2. 声明 Native 方法 public native String stringFromJNI(); public native int calculate(int a, int b, String operation); Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 3. 调用 Native 方法 String message stringFromJNI(); int result calculate(10, 20, add); TextView tv findViewById(R.id.textView); tv.setText(message \nResult: result); } }5.2 C 层// native-lib.cpp #include jni.h #include string #include android/log.h #define TAG NativeLib #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__) // 实现函数 static jstring native_stringFromJNI(JNIEnv* env, jobject thiz) { std::string hello Hello from C; return env-NewStringUTF(hello.c_str()); } static jint native_calculate(JNIEnv* env, jobject thiz, jint a, jint b, jstring operation) { // 获取操作类型 const char* op env-GetStringUTFChars(operation, nullptr); int result 0; if (strcmp(op, add) 0) { result a b; } else if (strcmp(op, subtract) 0) { result a - b; } else if (strcmp(op, multiply) 0) { result a * b; } else if (strcmp(op, divide) 0) { result b ! 0 ? a / b : 0; } env-ReleaseStringUTFChars(operation, op); LOGD(Calculate: %d %s %d %d, a, op, b, result); return result; } // 方法映射表 static JNINativeMethod gMethods[] { {stringFromJNI, ()Ljava/lang/String;, (void*)native_stringFromJNI}, {calculate, (IILjava/lang/String;)I, (void*)native_calculate}, }; // JNI_OnLoad库加载时调用 JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env; if (vm-GetEnv((void**)env, JNI_VERSION_1_6) ! JNI_OK) { return JNI_ERR; } // 查找 Java 类 jclass clazz env-FindClass(com/example/jnidemo/MainActivity); if (clazz nullptr) { return JNI_ERR; } // 注册 Native 方法 if (env-RegisterNatives(clazz, gMethods, sizeof(gMethods)/sizeof(gMethods[0])) 0) { return JNI_ERR; } LOGD(JNI_OnLoad success); return JNI_VERSION_1_6; }5.3 CMakeLists.txtcmake_minimum_required(VERSION 3.4.1) add_library( native-lib SHARED native-lib.cpp ) find_library( log-lib log ) target_link_libraries( native-lib ${log-lib} )六、总结JNI 连接 Java 和 C 的方式不是反射而是函数指针映射静态注册按命名规则JVM 用 dlsym 查找函数动态注册在 JNI_OnLoad 中手动注册函数指针调用过程Java → JVM 方法表 → C 函数指针 → 执行反射是什么运行时检查和操作程序结构动态调用方法、访问字段、创建对象应用框架、序列化、插件系统与 JNI 不同反射是 Java 调用 JavaJNI 是 Java 调用 C