安卓JNI(Java Native Interface)
JNI(Java Native Interface)即Java本地接口,是Java平台自1.1版本起纳入的标准技术,它充当Java代码与其他编程语言(以C/C++为主)之间的通信桥梁。从本质上来说,JNI并非Android专属技术,而是Java生态的一部分,Android系统通过引入JNI,实现了Java层应用与底层C/C++代码的交互能力。在Android系统中,Java运行在虚拟机之上,而操作系统的底层功能多由C/C++实现,JNI就成为了连接这两层的关键纽带,让Java代码能够调用系统底层的硬件操作、高性能算法等功能,同时也允许底层代码反向调用Java层逻辑。
在Android开发场景中,JNI的应用价值主要体现在以下四个方面:
(1)功能扩展:Java语言在硬件操作、系统底层调用等方面存在天然限制,通过JNI,开发者可以借助C/C++实现直接与硬件交互的功能,比如传感器数据读取、硬件驱动调用等,极大扩展了Android应用的能力边界。
(2)性能提升:对于计算密集型任务,如音视频编解码、图像渲染、复杂数学运算等,C/C++的执行效率远高于Java。将这些核心逻辑放在Native层实现,能够显著提升应用的运行性能,为用户带来更流畅的体验。
(3)代码复用:开发过程中常常会遇到需要使用成熟C/C++库的场景,比如开源的图像处理库OpenCV、加密算法库OpenSSL等。通过JNI,开发者无需将这些库重写为Java代码,直接调用即可,大大提高了开发效率。
(4)安全防护:C/C++代码编译生成的.so库相较于Java代码更难被逆向工程破解,将应用的核心算法、商业逻辑等关键代码放在Native层实现,能够有效提升代码的安全性,保护应用的核心技术机密。
native代码想要访问java虚拟机需要调用JNI方法,而获取JNI方法则通过JNI interface Pointer,它实际指向的就是一个都是指针的数组,每个指针指向的都是一个接口函数,接口函数可以使用C/C++等实现。

JNI注册有两种方式:静态注册和动态注册。
1.静态注册
当Java层调用native函数时,会在JNI库中根据函数名查找对应的JNI函数,如果没找到,会报错;如果找到了,则会在native函数与JNI函数之间建立关联关系,其实就是保存JNI函数的函数指针,就可以直接使用这个函数指针。
JNI静态注册的函数名必须按照格式:Java_ + 包名(如:com_example_jnidemo)+ 类名(如:JniDemo) + 函数名(如:helloFromC)
以下是静态注册的例子jnidemo.c:
/*** jnidemo.c* JNI Demo - C语言实现*/#include<stdio.h>#include<stdlib.h>#include<string.h>#include<jni.h>// 函数命名规则: Java_包名_类名_方法名// 包名中的点用下划线替换/*** 返回一个字符串给Java*/JNIEXPORT jstring JNICALLJava_com_example_jnidemo_JniDemo_helloFromC(JNIEnv *env, jobject this) {return (*env)->NewStringUTF(env, "Hello from C!");}/*** 两个整数相加*/JNIEXPORT jint JNICALLJava_com_example_jnidemo_JniDemo_add(JNIEnv *env, jobject this, jint a, jint b) {return a + b;}/*** 两个整数相乘*/JNIEXPORT jint JNICALLJava_com_example_jnidemo_JniDemo_multiply(JNIEnv *env, jobject this, jint a, jint b) {return a * b;}/*** 调用Java对象的方法*/JNIEXPORT jstring JNICALLJava_com_example_jnidemo_JniDemo_getStringFromJava(JNIEnv *env, jobject this) {// 获取Java类的classjclass cls = (*env)->GetObjectClass(env, this);// 获取方法IDjmethodID mid = (*env)->GetMethodID(env, cls, "getJavaString", "()Ljava/lang/String;");if (mid == NULL) {return (*env)->NewStringUTF(env, "Method not found!");}// 调用Java方法jobject result = (*env)->CallObjectMethod(env, this, mid);// 转换为jstring并返回return (jstring)result;}
静态注册的缺点:要求JNI函数的名字必须遵循JNI规范的命名格式,初次调用会根据函数名去搜索JNI中对应的函数,会影响执行效率;需要编译所有声明了native函数的Java类,每个所生成的class文件都要用javah工具生成一个头文件。
2.动态注册
JNI动态注册是通过提供一个函数映射表,注册给JVM虚拟机,这样JVM就可以用函数映射表来调用相应的函数,就不必通过函数名来查找需要调用的函数
Java与JNI通过JNINativeMethod的结构来建立函数映射表,它在jni.h头文件中定义,其结构内容如下:
typedef struct {const char* name;const char* signature;void* fnPtr;} JNINativeMethod;
(1)name:这是一个 const char* 类型的字符串,表示 Java 层中声明的 native 方法的名称。例如,如果 Java 代码中有一个方法为 public native int add(int a, int b);,那么 name 字段的值就应该是 “add”。(2)signature:这也是一个 const char* 类型的字符串,它描述了 Java 方法的参数类型和返回值类型。签名字符串遵循特定的格式,括号内是参数类型列表,括号后是返回值类型。参数和返回值类型使用特定的字符表示,例如:
V 表示 void (无返回值);I 表示 int;J 表示 long;Z 表示 boolean;Ljava/lang/String; 表示 java.lang.String 类型对象;[I 表示 int[] 类型数组。
([Ljava/lang/String;)V 表示接受一个 String[] 数组作为参数,返回 void 的方法。
签名字符串的格式是 “(参数类型列表)返回值类型”。例如,对于方法 int add(int a, int b),其签名应为 “(II)I”;对于方法 void close(),其签名应为 “()V”。
(3)fnPtr:这是一个 void* 类型的指针,指向在 C/C++ 层实现的对应 native 函数。在定义 JNINativeMethod 数组时,需要将函数的地址赋值给 fnPtr,并且通常需要强制转换为 (void*) 类型。例如,如果 C/C++ 中实现的函数名为 int native_add(JNIEnv* env, jobject thiz, jint a, jint b),那么在 JNINativeMethod 数组中,fnPtr 字段的值应该是 (void*)native_add。
创建映射表后,调用RegisterNatives函数将映射表注册给JVM,以下是动态注册的例子:
/*** jnidemo.c* JNI Demo - C 语言实现(动态注册方式)* 使用 RegisterNatives 进行动态注册,无需遵循 Java_包名_类名_方法名 的命名规则*/#include<stdio.h>#include<stdlib.h>#include<string.h>#include<jni.h>// 定义 Java 类路径#define JNIDEMO_CLASS_PATH "com/example/jnidemo/JniDemo"// 声明静态方法(不再使用 JNIEXPORT 和 Java_前缀命名)static jstring helloFromC(JNIEnv *env, jobject thiz);static jint add(JNIEnv *env, jobject thiz, jint a, jint b);static jint multiply(JNIEnv *env, jobject thiz, jint a, jint b);static jstring getStringFromJava(JNIEnv *env, jobject thiz);// 动态注册方法表// 格式:{ Java 中的方法名,方法签名,C 函数指针 }static JNINativeMethod gMethods[] = {{"helloFromC", "()Ljava/lang/String;", (void *)helloFromC},{"add", "(II)I", (void *)add},{"multiply", "(II)I", (void *)multiply},{"getStringFromJava", "()Ljava/lang/String;", (void *)getStringFromJava},};/*** 返回一个字符串给 Java*/static jstring helloFromC(JNIEnv *env, jobject thiz){return (*env)->NewStringUTF(env, "Hello from C!");}/*** 两个整数相加*/static jint add(JNIEnv *env, jobject thiz, jint a, jint b){return a + b;}/*** 两个整数相乘*/static jint multiply(JNIEnv *env, jobject thiz, jint a, jint b){return a * b;}/*** 调用 Java 对象的方法*/static jstring getStringFromJava(JNIEnv *env, jobject thiz){// 获取 Java 类的 classjclass cls = (*env)->GetObjectClass(env, thiz);// 获取方法 IDjmethodID mid = (*env)->GetMethodID(env, cls, "getJavaString", "()Ljava/lang/String;");if (mid == NULL) {return (*env)->NewStringUTF(env, "Method not found!");}// 调用 Java 方法jobject result = (*env)->CallObjectMethod(env, thiz, mid);// 转换为 jstring 并返回return (jstring)result;}/*** 注册本地方法* 在 System.loadLibrary() 调用时自动执行*/JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved){JNIEnv *env = NULL;jclass cls = NULL;jint result = JNI_ERR;// 获取 JNIEnv 指针if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6) != JNI_OK) {return JNI_ERR;}// 查找 Java 类cls = (*env)->FindClass(env, JNIDEMO_CLASS_PATH);if (cls == NULL) {printf("Class not found: %s\n", JNIDEMO_CLASS_PATH);return JNI_ERR;}// 注册本地方法// 参数:方法表,方法数量if ((*env)->RegisterNatives(env, cls, gMethods, sizeof(gMethods) / sizeof(gMethods[0])) != JNI_OK) {printf("RegisterNatives failed!\n");return JNI_ERR;}printf("JNI methods registered successfully!\n");result = JNI_VERSION_1_6;return result;}/*** 可选:卸载时清理注册的方法*/JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved){JNIEnv *env = NULL;jclass cls = NULL;// 获取 JNIEnv 指针if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_6) != JNI_OK) {return;}// 查找 Java 类cls = (*env)->FindClass(env, JNIDEMO_CLASS_PATH);if (cls == NULL) {return;}// 注销本地方法(*env)->UnregisterNatives(env, cls);printf("JNI methods unregistered!\n");}
动态注册实现步骤总结如下:
(1)利用结构体 JNINativeMethod 数组记录 java 方法与 JNI 函数的对应关系;
(2)实现 JNI_OnLoad 方法,在加载动态库后,执行动态注册;
(3)调用 FindClass 方法,获取 java 对象;
(4)调用 RegisterNatives方法,传入 java 对象,以及 JNINativeMethod 数组,以及注册数目完成注册;
3.Java与JNI类型转换
Java 的基本数据类型在 JNI 中都有对应的本地类型,这些类型名称通常在 Java 类型前加上 ‘j’ 前缀。Java 的引用类型在 JNI 中也有对应的类型,这些类型都继承自 jobject,这是 JNI 中所有对象类型的基类。
|
|
|
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| Java引用类型 | JNI类型 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4. Java调用native方法
以上静态注册或者动态注册方法jnidemo.c编译生成libjnidemo.so库,Java去使用native方法的实现如下:
/***********JniDemo.java*************/package com.example.jnidemo;/*** JNI Demo - 演示Java调用C/C++本地方法*/public class JniDemo {// 加载本地库static {System.loadLibrary("jnidemo");}// 声明native方法public native String helloFromC();public native intadd(int a, int b);public native intmultiply(int a, int b);public native String getStringFromJava();// 供C调用的Java方法public String getJavaString() {return "This is a string from Java!";}publicstaticvoidmain(String[] args) {JniDemo demo = new JniDemo();System.out.println("=== JNI Demo ===");System.out.println("Hello from C: " + demo.helloFromC());System.out.println("5 + 3 = " + demo.add(5, 3));System.out.println("4 * 6 = " + demo.multiply(4, 6));System.out.println("String from Java via JNI: " + demo.getStringFromJava());}}
在Java类中声明需要由Native层实现的方法,使用native关键字修饰,同时在静态代码块中加载编译生成的Native库。需要注意的是,加载库时只需要填写库名,不需要添加前缀lib和后缀.so。
5. 总结
JNI技术作为Android开发中的重要组成部分,为开发者提供了连接Java层与Native层的桥梁,极大地扩展了Android应用的开发能力。无论是追求极致性能、复用现有代码还是实现特殊功能,JNI都能发挥关键作用。
夜雨聆风