文章目录
-
- [一、JNI 概述](#一、JNI 概述)
-
- [1.1 什么是 JNI](#1.1 什么是 JNI)
- [1.2 为什么需要 JNI](#1.2 为什么需要 JNI)
- [1.3 JNI 的实现](#1.3 JNI 的实现)
- [二、JNI 开发基础](#二、JNI 开发基础)
-
- [2.1 开发环境配置](#2.1 开发环境配置)
- [2.2 JNI 基本工作流程](#2.2 JNI 基本工作流程)
-
- [2.2.1 Java 调用 Native 代码](#2.2.1 Java 调用 Native 代码)
- [2.2.2 Native 代码调用 Java](#2.2.2 Native 代码调用 Java)
- 参考资料
一、JNI 概述
1.1 什么是 JNI
JNI 是一套标准规范,允许运行在 Java 虚拟机中的 Java/Kotlin 代码 与能够与本地代码 (Native Code,通常是 C 或 C++) 进行安全、高效的双向通信。
JNI 规范定义的主要内容有:
- 数据类型映射规范:定义了Java类型与本地C/C++类型之间的完整映射关系,包括基本类型(如jint对应Java的int)、引用类型(如jobject对应Java对象)以及数组类型(如jintArray)。
- 方法命名和注册规范 : 规定了本地方法的命名规则,采用"Java_*包名_*类名_方法名"的格式。同时定义了静态注册和动态注册两种方式,以及 JNIEXPORT 和 JNICALL 等宏定义的使用规范。
- 函数接口表规范: JNI 通过JNIEnv指针提供了一套完整的函数接口表,包含了访问Java对象、调用Java方法、处理异常、管理内存引用等所有必要操作。每个线程都有独立的 JNIEnv 实例。
- 引用管理规范: 定义了局部引用、全局引用和弱全局引用三种引用类型的管理机制,以及相应的创建、删除和生命周期管理规则,确保内存安全。
- 异常处理规范: 规范了如何在本地代码中抛出和捕获Java异常,以及异常检查和处理的标准流程。
- 线程管理规范: 定义了本地线程如何附加到Java虚拟机,以及线程本地存储的管理方式。
- 字符串和数组操作规范: 提供了UTF-8和UTF-16字符串的转换机制,以及数组元素的访问和修改规范。
1.2 为什么需要 JNI
- 性能关键:对于图形渲染、物理模拟、音视频编解码等计算密集型任务,C/C++ 代码通常能提供比 Java/Kotlin 更高的执行效率。
- 重用现有库:有大量成熟、高性能的 C/C++ 开源库(如 OpenCV, FFmpeg, SQLite)。通过 JNI,可以直接在 Android 应用中使用这些库,避免重复造轮子。
- 安全性:某些底层系统调用或硬件特性,可能无法通过标准的 Android SDK 直接访问,需要借助 Native 代码。
- 硬件操控: 直接操作特定硬件(如传感器底层、GPU 加速)往往需要 C/C++ 的底层访问能力。
1.3 JNI 的实现
Android 对 JNI 的支持主要体现在:
- 系统运行时实现 :JNI 的实现位于 Android 源码的
frameworks/base/core/jni/目录下,这些代码会被编译成libandroid_runtime.so动态库,放置在目标系统的/system/lib目录中。 - 开发工具支持 :为方便开发者编写符合 JNI 规范的代码,Android 官方提供了 NDK(Native Development Kit) 工具集。NDK可以将符合 JNI 规范的代码最终生成可在 Android 系统上运行的本地库。其中,
$NDK_HOME/toolchains/llvm/prebuilt/<host-arch>/sysroot/usr/include/jni.h为 JNI 开发阶段的接口声明,定义了所有 JNI 类型和函数。
二、JNI 开发基础
2.1 开发环境配置
- 开发工具: NDK
- 构建系统:CMake(推荐) 或者 ndk-build
2.2 JNI 基本工作流程
JNI 的核心是实现 Java 与 C/C++ 代码的双向通信,其工作流程主要包含两个方向。
2.2.1 Java 调用 Native 代码
具体步骤如下:
- 声明 native 方法 :在 Java 类中使用
native关键字声明方法。
java
public native String stringFromJNI();
- 加载本地库 :使用
System.loadLibrary()加载包含实现的动态库。
java
static {
System.loadLibrary("nativeapptest");
}
- 绑定注册函数 :在 C/C++ 中,按照 JNI 规范实现函数。假设使用静态函数注册方式,当 Java 调用
stringFromJNI()时,虚拟机会根据命名规则自动找到并执行对应的 C/C++ 函数。本地函数示例如下:
cpp
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_nativeapptest_MainActivity_stringFromJNI(
JNIEnv* env,
jobject thiz) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
其中,
-
extern "C"是C++中的一个链接指示符,用于解决C++和C语言之间的名称修饰(Name Mangling)问题。 -
- C++编译器:为了支持函数重载、类、命名空间等特性,会对函数名进行"修饰",添加额外的信息(如参数类型、命名空间等)
- C编译器:不会对函数名进行修饰,保持原始函数名
-
JNIEXPORT:指定函数的可见性和导出属性,确保函数能够被Java虚拟机找到和调用 -
JNICALL:指定函数的调用约定(Calling Convention),确保参数传递和栈清理的一致性 -
Java_com_example_nativeapptest_MainActivity_stringFromJNI- 函数名(遵循JNI命名规范) -
JNIEnv *env: JNI(Java Native Interface)环境指针,是 native 代码与 Java 代码交互的核心接口 -
jobject thiz- JNI环境参数和调用对象,用于调用Java方法、访问Java对象、操作Java数据等
这两个宏确保了native函数在不同平台上的正确导出和调用。
2.2.2 Native 代码调用 Java
在某些情况下(如异步回调),也需要从 Native 层主动调用 Java 层的方法。此过程不需要"注册",但需遵循明确的查找和调用步骤,
Native 代码调用 Java 的核心步骤如下:
- 获取 JNIEnv 指针:本地代码通过 JNIEnv 接口指针访问 Java 功能。
- **查找目标类:**使用
FindClass通过类名获取类的引用 (jclass)。 - **获取方法ID:**使用
GetMethodID或GetStaticMethodID,通过方法名和类型签名获取方法的引用 (jmethodID)。 - 调用 Java 方法 :通过
Call<Type>Method系列函数进行实际调用。
示例如下:
(1) Java 类:提供回调方法
java
public class JavaClassToCall {
// 实例方法:字符串处理
public String processString(String input) {
return "Processed: " + input.toUpperCase();
}
// 静态方法:数学计算
public static int addNumbers(int a, int b) {
return a + b;
}
// 无返回值方法
public void logMessage(String message) {
System.out.println("[JAVA] " + message);
}
}
(2) C/C++ :查找并调用 Java 方法
cpp
#include <jni.h>
#include <string>
// 本地代码调用Java方法的示例
void callJavaMethods(JNIEnv* env, jobject callingObject) {
// 1. 获取JNIEnv指针(通常由调用方传入)
// 2. 查找Java类
jclass javaClass = env->FindClass("com/example/JavaClassToCall");
if (javaClass == nullptr) {
// 处理类找不到的情况
return;
}
// === 调用实例方法 ===
// 2.1 获取实例方法ID
jmethodID processStringMethod = env->GetMethodID(
javaClass,
"processString",
"(Ljava/lang/String;)Ljava/lang/String;");
if (processStringMethod != nullptr) {
// 3.1 调用实例方法
jstring inputStr = env->NewStringUTF("hello from native");
jstring resultStr = (jstring)env->CallObjectMethod(
callingObject,
processStringMethod,
inputStr);
// 处理返回的字符串
const char* resultCStr = env->GetStringUTFChars(resultStr, nullptr);
printf("Java returned: %s\n", resultCStr);
env->ReleaseStringUTFChars(resultStr, resultCStr);
env->DeleteLocalRef(inputStr);
env->DeleteLocalRef(resultStr);
}
// === 调用静态方法 ===
// 2.2 获取静态方法ID
jmethodID addNumbersMethod = env->GetStaticMethodID(
javaClass,
"addNumbers",
"(II)I");
if (addNumbersMethod != nullptr) {
// 3.2 调用静态方法
jint sum = env->CallStaticIntMethod(
javaClass,
addNumbersMethod,
10, 20);
printf("Java static method returned: %d\n", sum);
}
// === 调用无返回值方法 ===
// 2.3 获取void方法ID
jmethodID logMessageMethod = env->GetMethodID(
javaClass,
"logMessage",
"(Ljava/lang/String;)V");
if (logMessageMethod != nullptr) {
// 3.3 调用void方法
jstring message = env->NewStringUTF("Message from native code");
env->CallVoidMethod(
callingObject,
logMessageMethod,
message);
env->DeleteLocalRef(message);
}
// 4. 释放本地引用
env->DeleteLocalRef(javaClass);
}