Android JNI 入门

916 阅读7分钟

JNI 是什么

JNI(Java Native Interface) 是 Java 平台提供的一种机制,用于实现 Java 代码与本地(Native)代码(如 C/C++)的交互。它允许 Java 程序调用本地库的函数,也允许本地代码调用 Java 方法,是 Java 跨平台特性与本地高性能代码结合的桥梁。 JNI的核心作用

  1. Java调用本地代码
  2. 本地代码调用Java

JNI的应用场景

场景说明
性能敏感操作图像处理、音视频编解码等高性能计算
硬件访问直接操作硬件
复用现有C/C++库集成历史遗留代码或第三方库
系统级功能扩展调用操作系统API

几个关键术语

特性JavaVMJNIEnv
作用域全局(整个虚拟机生命周期)线程局部(每个线程独立)
获取方式通过 JNI_OnLoad 或 JNI_CreateJavaVM 获取通过 JavaVM->GetEnv() 或 JNI_OnLoad 参数传入
线程安全可跨线程共享不能跨线程使用(每个线程需单独获取)
主要用途管理虚拟机生命周期、跨线程获取 JNIEnv执行 JNI 操作(调用 Java 方法、访问字段等)

Java数据类型和Native数据类型之间的映射关系

引用类型的映射关系 Java 对象类型在 JNI 中通过特定引用类型表示:

  • Object → jobject
  • String → jstring
  • Class → jclass
  • Array → jarray(如 int[] → jintArray
  • 其他对象类型均映射为 jobject,需通过 JNI 函数操作(如 GetFieldIDCallObjectMethod) 基础类型的映射关系
  • boolean → jboolean
  • byte → jbyte
  • char → jchar
  • short → jshort
  • int → jint
  • long → jlong
  • float → jfloat
  • double → jdouble
  • void → void 方法签名映射
Java类型签名符号示例(方法签名)
intIint add(int,int) -> (II)I
booleanZvoid setFlag(Z) -> (Z)V
StringLjava/lang/String;String getName() -> ()Ljava/lang/String;
Object[][Ljava/lang/Object;]-
voidVvoid log() -> ()V

创建项目,可以直接在创建时选择 "Native C++"

Pasted image 20250511183515.png

我这里在标准的android项目中添加JNI的支持。

Pasted image 20250511184954.png

如何用(Java)

下面看一下如何用java代码实现的Java和Native的交互。

  1. 声明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);  
    }  
}
  1. 加载library
public class JniClass {
    static {  
        System.loadLibrary("JavaJNI");  
    }  
}
  1. 实现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;  
}
  1. 调用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);
  1. 操作结果
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是该对象的类对象。

这样的名字又长又难记,下面介绍一种动态注册的映射关系的方式。

  1. 声明对应的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
}
  1. 重写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;  
}

源码: gitee.com/codetoolmak…