一、JNI 基础概念
1.1 什么是 JNI?
JNI (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 mangling)JNIEXPORT:导出符号,让 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++ 类型 |
|---|---|---|
| boolean | Z | jboolean |
| byte | B | jbyte |
| char | C | jchar |
| short | S | jshort |
| int | I | jint |
| long | J | jlong |
| float | F | jfloat |
| double | D | jdouble |
| void | V | void |
| String | Ljava/lang/String; | jstring |
| int[] | [I | jintArray |
| Object[] | [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 什么是 JNIEnv?
JNIEnv是 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 调用 Java | Java 调用 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.txt
cmake_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 调用 Java,JNI 是 Java 调用 C++