JNI 是什么
JNI(Java Native Interface) 是 Java 平台提供的一种机制,用于实现 Java 代码与本地(Native)代码(如 C/C++)的交互。它允许 Java 程序调用本地库的函数,也允许本地代码调用 Java 方法,是 Java 跨平台特性与本地高性能代码结合的桥梁。 JNI的核心作用
- Java调用本地代码
- 本地代码调用Java
JNI的应用场景
| 场景 | 说明 |
|---|---|
| 性能敏感操作 | 图像处理、音视频编解码等高性能计算 |
| 硬件访问 | 直接操作硬件 |
| 复用现有C/C++库 | 集成历史遗留代码或第三方库 |
| 系统级功能扩展 | 调用操作系统API |
几个关键术语
| 特性 | JavaVM | JNIEnv |
|---|---|---|
| 作用域 | 全局(整个虚拟机生命周期) | 线程局部(每个线程独立) |
| 获取方式 | 通过 JNI_OnLoad 或 JNI_CreateJavaVM 获取 | 通过 JavaVM->GetEnv() 或 JNI_OnLoad 参数传入 |
| 线程安全 | 可跨线程共享 | 不能跨线程使用(每个线程需单独获取) |
| 主要用途 | 管理虚拟机生命周期、跨线程获取 JNIEnv | 执行 JNI 操作(调用 Java 方法、访问字段等) |
Java数据类型和Native数据类型之间的映射关系
引用类型的映射关系 Java 对象类型在 JNI 中通过特定引用类型表示:
Object→jobjectString→jstringClass→jclassArray→jarray(如int[]→jintArray)- 其他对象类型均映射为
jobject,需通过 JNI 函数操作(如GetFieldID、CallObjectMethod) 基础类型的映射关系 boolean→jbooleanbyte→jbytechar→jcharshort→jshortint→jintlong→jlongfloat→jfloatdouble→jdoublevoid→void方法签名映射
| Java类型 | 签名符号 | 示例(方法签名) |
|---|---|---|
| int | I | int add(int,int) -> (II)I |
| boolean | Z | void setFlag(Z) -> (Z)V |
| String | Ljava/lang/String; | String getName() -> ()Ljava/lang/String; |
| Object[] | [Ljava/lang/Object;] | - |
| void | V | void log() -> ()V |
创建项目,可以直接在创建时选择 "Native C++"
我这里在标准的android项目中添加JNI的支持。
如何用(Java)
下面看一下如何用java代码实现的Java和Native的交互。
- 声明native方法
public class JniClass {
private static final String TAG = "JNI_TAG_JAVA";
private static int VERSION = 1000;
public static String testStaticString(String test) {
Log.e(TAG, "testStaticString, " + test);
return "SUCCESS";
}
// region native层 实现
private String name = "Java"; // 先在native层读取,然后在native层修改其值,最后再调用java层的方法输出
public static native String getStringFromNative();
public native void hello(String name);
public native int calc(int first, int second);
// endregion
public void test() {
Log.e(TAG, "test name : " + name + ",version : " + VERSION);
}
}
- 加载library
public class JniClass {
static {
System.loadLibrary("JavaJNI");
}
}
- 实现native方法
#include <jni.h>
#include <android/log.h>
// 定义日志标签(通常用项目名)
#define LOG_TAG "JNI_TAG_NATIVE"
// 定义不同级别的日志宏
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_javajni_jni_JniClass_getStringFromNative(JNIEnv *env, jclass clazz) {
LOGE("Java_com_example_javajni_jni_JniClass_getStringFromNative");
jstring str = env->NewStringUTF("hello from jni");
return str;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_javajni_jni_JniClass_hello(JNIEnv *env, jobject thiz, jstring name) {
const char *str = env->GetStringUTFChars(name, nullptr);
LOGE("Java_com_example_javajni_jni_JniClass_hello,params : %s", str);
// 获取 java 中定义的静态属性
// 1. 查找类文件
jclass jniClass = env->FindClass("com/example/javajni/jni/JniClass");
if (jniClass == nullptr) {
LOGE("can not find class : com.example.javajni.jni.JniClass");
return;
}
// 2. 查找属性, 第一个参数是类,第二个参数是属性名,第三个参数是属性的签名
jfieldID tag = env->GetStaticFieldID(jniClass, "TAG", "Ljava/lang/String;");
if (tag == nullptr) {
LOGE("can not find TAG property.");
} else {
jstring tagStr = static_cast<jstring>(env->GetStaticObjectField(jniClass, tag));
const char *string = env->GetStringUTFChars(tagStr, nullptr);
LOGE("jni class TAG : %s", string);
env->ReleaseStringUTFChars(tagStr, string);
}
jfieldID version = env->GetStaticFieldID(jniClass, "VERSION", "I");
if (version == nullptr) {
LOGE("can not find VERSION property.");
} else {
jint versionInt = env->GetStaticIntField(jniClass, version);
LOGE("jni class VERSION : %d", versionInt);
// 修改 VERSION 的值
env->SetStaticIntField(jniClass, version, 20000);
}
// 获取 java 中定义的实例属性
jfieldID nameFileldId = env->GetFieldID(jniClass, "name", "Ljava/lang/String;");
if (nameFileldId == nullptr) {
LOGE("can not find name property.");
} else {
jstring nameStr = static_cast<jstring>(env->GetObjectField(thiz, nameFileldId));
const char *string = env->GetStringUTFChars(nameStr, nullptr);
LOGE("jni class name : %s", string);
env->ReleaseStringUTFChars(nameStr, string);
// 修改name的值
jstring native = env->NewStringUTF("Native"); // 不需要手动释放
env->SetObjectField(thiz, nameFileldId, native);
}
// 调用 java 方法输出静态属性和实例属性
jmethodID testMethod = env->GetMethodID(jniClass, "test", "()V");
if (testMethod == nullptr) {
LOGE("can not find method test()");
} else {
env->CallVoidMethod(thiz, testMethod);
}
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_javajni_jni_JniClass_calc(JNIEnv *env, jobject thiz, jint first, jint second) {
LOGE("Java_com_example_javajni_jni_JniClass_calc,params first: %d, params second: %d", first,
second);
return first + second;
}
- 调用native方法
JniClass jniClass = new JniClass();
int result = jniClass.calc(1, 2);
Log.e(TAG, "onClick: add result (1+2) = " + result);
jniClass.hello("MainActivity");
String stringFromNative = JniClass.getStringFromNative();
Log.e(TAG, "onClick: native return " + stringFromNative);
- 操作结果
2025-05-11 22:18:29.053 20459-20459 JNI_TAG_NATIVE com.example.javajni E Java_com_example_javajni_jni_JniClass_calc,params first: 1, params second: 2
2025-05-11 22:18:29.053 20459-20459 MainActivity com.example.javajni E onClick: add result (1+2) = 3
2025-05-11 22:18:29.053 20459-20459 JNI_TAG_NATIVE com.example.javajni E Java_com_example_javajni_jni_JniClass_hello,params : MainActivity
2025-05-11 22:18:29.053 20459-20459 JNI_TAG_NATIVE com.example.javajni E jni class TAG : JNI_TAG_JAVA
2025-05-11 22:18:29.053 20459-20459 JNI_TAG_NATIVE com.example.javajni E jni class VERSION : 1000
2025-05-11 22:18:29.053 20459-20459 JNI_TAG_NATIVE com.example.javajni E jni class name : Java
2025-05-11 22:18:29.053 20459-20459 JNI_TAG_JAVA com.example.javajni E test name : Native,version : 20000
2025-05-11 22:18:29.053 20459-20459 JNI_TAG_NATIVE com.example.javajni E Java_com_example_javajni_jni_JniClass_getStringFromNative
2025-05-11 22:18:29.053 20459-20459 MainActivity com.example.javajni E onClick: native return hello from jni
CMakeLists.txt
# CMake最低版本号
cmake_minimum_required(VERSION 3.22.1)
# 工程名字
project("JavaJNI")
# 添加库:名字为JavaJNI的动态库。最终生产产物名称后缀为.so,参与编译的源码是 JavaJNI.cpp
add_library(${CMAKE_PROJECT_NAME} SHARED
# List C/C++ source files with relative paths to this CMakeLists.txt.
JavaJNI.cpp)
# 链接到当前so库,以及android库(可以使用android的一些api),log库(输出log的)
target_link_libraries(${CMAKE_PROJECT_NAME}
# List libraries link to the target library
android
log)
如何用(Kotlin)
下面看一下如何用java代码实现的Kotlin和native的交互。
Kotlin中没有
native关键字,而是用的external
kotlin的静态方法的动态注册和java的不太一样,kotlin的静态方法是声明在它的伴生对象内,所以要这么写
jstring getStringFromNative(JNIEnv *env, jobject clazz) {
LOGE("getStringFromNative");
jstring str = env->NewStringUTF("hello from jni");
return str;
}
// 或者这么写
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_kotlinjni_jni_JniClass_00024Companion_getStringFromNative(JNIEnv *env, jobject thiz) {
// TODO: implement getStringFromNative()
}
动态注册时这么写
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){
JNIEnv *env = nullptr;
if((*vm).GetEnv((void **)&env,JNI_VERSION_1_6) != JNI_OK){
LOGE("get env failed.");
return -1;
}
jclass companionClazz = env->FindClass("com/example/kotlinjni/jni/JniClass$Companion");
JNINativeMethod gMethods[] = {
{"getStringFromNative", "()Ljava/lang/String;", (void *) getStringFromNative},
};
if(env->RegisterNatives(companionClazz,gMethods,sizeof(gMethods)/ sizeof(JNINativeMethod)) < 0 ){
LOGE("register natives failed. getStringFromNative");
return -1;
}
// 成员方法的部分没什么变化
return JNI_VERSION_1_6;
}
高级一点的用法
上面我们都是用android studio 工具的功能帮我们自动生成的映射关系。
Java_<包名全路径>_<类名>_<方法名>
比如我们的JniClass,
全名是:com.example.jni.javajni.JniClass
成员native方法声明是:public native void test();
则自动生成的方法名是:
Java_com_example_jni_javajni_JniClass_test(JNIEnv* env,jobject thiz) 如果有参数,就追加在jobject后面,其中JNIEnv表示当前的线程相关环境,thiz是该对象本身。
静态成员native方法声明:public static native void test();
Java_com_example_jni_javajni_JniClass_test(JNIEnv* env,jclass clazz)
如果有参数,就追加在jobject后面,其中JNIEnv表示当前的线程相关环境,clazz是该对象的类对象。
这样的名字又长又难记,下面介绍一种动态注册的映射关系的方式。
- 声明对应的native函数
jstring getStringFromNative(JNIEnv *env, jclass clazz) {
//TODO
}
void hello(JNIEnv *env, jobject thiz, jstring name) {
//TODO
}
jint calc(JNIEnv *env, jobject thiz, jint first, jint second) {
//TODO
}
- 重写JNI_OnLoad函数
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved){
JNIEnv *env = nullptr;
if((*vm).GetEnv((void **)&env,JNI_VERSION_1_6) != JNI_OK){
LOGE("get env failed.");
return -1;
}
jclass clazz = env->FindClass("com/example/javajni/jni/JniClass");
if(clazz == nullptr){
LOGE("can find class \"com/example/javajni/jni/JniClass\"");
return -1;
}
JNINativeMethod gMethods[] = {
{"getStringFromNative", "()Ljava/lang/String;", (void *) getStringFromNative},
};
if(env->RegisterNatives(clazz,gMethods,sizeof(gMethods)/ sizeof(JNINativeMethod)) < 0 ){
LOGE("register natives failed. getStringFromNative");
return -1;
}
JNINativeMethod methods[] = {
{"hello", "(Ljava/lang/String;)V", (void *) hello},
{"calc", "(II)I", (void *) calc},
};
if(env->RegisterNatives(clazz,methods,sizeof(methods)/ sizeof(JNINativeMethod)) < 0 ){
LOGE("register natives failed. getStringFromNative");
return -1;
}
return JNI_VERSION_1_6;
}