乐于分享
好东西不私藏

安卓JNI(Java Native Interface)

安卓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类的class    jclass cls = (*env)->GetObjectClass(env, this);    // 获取方法ID    jmethodID 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 类的 class    jclass cls = (*env)->GetObjectClass(env, thiz);     // 获取方法 ID    jmethodID 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类型
boolean(布尔类型)
jboolean
byte
jbyte
char
jchar
short
jshort
int
jint
long
jlong
float
jfloat
double
jdouble
Java引用类型 JNI类型
All objects
jobject
java.lang.class
jclass
java.lang.String
jstring
java.lang.Throwable
jthrowable
Object[]
jobjectArray
boolean[]
jbooleanArray
byte[]
jbyteArray
….

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(53));        System.out.println("4 * 6 = " + demo.multiply(46));        System.out.println("String from Java via JNI: " + demo.getStringFromJava());    }}

在Java类中声明需要由Native层实现的方法,使用native关键字修饰,同时在静态代码块中加载编译生成的Native库。需要注意的是,加载库时只需要填写库名,不需要添加前缀lib和后缀.so

5. 总结

JNI技术作为Android开发中的重要组成部分,为开发者提供了连接Java层与Native层的桥梁,极大地扩展了Android应用的开发能力。无论是追求极致性能、复用现有代码还是实现特殊功能,JNI都能发挥关键作用。

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » 安卓JNI(Java Native Interface)

猜你喜欢

  • 暂无文章