目 录
JNI(Java Native Interface)是一种编程框架,允许Java代码和其他语言(如C和C++)编写的代码进行交互。它在Java和原生代码之间提供了一个桥梁,使得开发者可以在Java应用程序中调用和访问原生代码(通常是C或C++代码),同时也可以从原生代码中调用Java代码。
JNI的主要目的是在Java虚拟机(JVM)中嵌入本地代码。通过JNI,可以在Java代码中声明本地方法,在本地方法实现中编写底层的本地代码。这样,Java代码可以直接调用本地方法,而本地方法中的本地代码可以与操作系统和硬件交互,执行一些Java无法直接完成的任务,例如访问系统资源、调用特定的库函数等。
一、核心要素
1. Native 方法声明
JAVA代码中声明native方法,在 C/C++ 代码中进行方法实现。
public class MainActivity extends AppCompatActivity {
private String name = "Chesnut";
// 声明native方法
public native void changeName();
public static native void changeAge();
}
注意事项 :
- 访问权限修饰符:native方法可以被public、protected、private修饰,其访问规则与普通 Java 方法一致。
- 静态与实例方法:支持声明静态native方法,格式为public static native String getVersion(),调用时无需实例化对象,通过 类名.方法名() 即可调用。
- 参数与返回值:可以包含任意 Java 数据类型的参数和返回值(基本类型、引用类型均可),但需遵循 JNI 数据类型映射规则。
- 方法名避免特殊字符:native方法名若包含下划线 “_”,生成的 JNI 函数名需通过转义处理(如native void a_b()对应 JNI 函数Java_xxx_a_1b),建议命名时尽量使用驼峰式。
2. JNI函数命名规则
JNI 函数的命名有特定的规则,以便 Java 虚拟机能够正确识别和关联对应的native方法。其基本格式为:Java_<包名>_<类名>_<方法名>,其中包名中的 “.” 需要替换为 “_”。
Java_com_example_jnidemo_MainActivity_changeName(
JNIEnv * env, // jni桥梁
jobject thiz) { }
- 包名转义:若包名包含下划线(如com.example.my_jni),需在 JNI 函数名中用 “_1” 代替下划线。
- 内部类处理:若native方法属于内部类,类名需用 “ ”连接外部类与内部类。
- 函数修饰符:JNI 函数必须添加JNIEXPORT和JNICALL宏(定义在jni.h中),确保函数能被 Java 虚拟机识别和调用。
3. JNIEnv
JNIEnv是 JNI 接口指针,它是一个指向函数表的指针,包含了大量的 JNI 函数,用于在 C/C++ 代码中操作 Java 对象、调用 Java 方法等。在 JNI 函数中,第一个参数通常是JNIEnv*。
- 字符串处理:NewStringUTF、GetStringUTFChars、ReleaseStringUTFChars
- 类操作:FindClass、GetObjectClass
- 字段操作:GetFieldID、GetIntField、SetIntField
- 方法操作:GetMethodID、CallIntMethod
extern "C" // 下面代码采用C的编译方法
JNIEXPORT //JNIEXPORT JNI重要关键字,不能少
void JNICALL // JNICALL关键字,用于约束函数入栈顺序和堆栈内存清理的规则
// Java 包名 com_xxx_1xxx 类名 方法名
// jobject: MainActivity this 实例调用
// jclss:MainActivity class 类调用
Java_com_example_jnidemo_MainActivity_changeName(
JNIEnv * env, // jni桥梁
jobject thiz) {
// C语言中的JNIEnv * env是二级指针
// (*env)->DeleteLocalRef(NULL);
// C++中的JNIEnv * env是一级指针
env->DeleteLocalRef(NULL);
}
注意事项:
- C 与 C++ 语法差异:C 语言中需显式解引用函数表,如(*env)->NewStringUTF(env, "test");C++ 中env被封装为类指针,可直接调用env->NewStringUTF("test")。
- 避免野指针:JNIEnv的生命周期与当前线程绑定,线程结束后env指针失效,继续使用会导致内存访问错误。
- 检查函数返回值:部分 JNI 函数在失败时返回NULL(如FindClass找不到类时),需添加判空逻辑避免崩溃。
4.jobject 类型
jobject代表 Java 对象在 JNI 中的引用。在实例方法的 JNI 函数中,第二个参数是jobject类型,它指向调用该方法的 Java 对象本身。而在静态方法的 JNI 函数中,第二个参数是jclass类型,指向该静态方法所属的类。jobject可强制转换为更具体的引用类型(如jstring、jarray),但需确保类型匹配,否则会导致运行时错误。
extern "C"
JNIEXPORT void JNICALL
Java_com_example_jnidemo_MainActivity_changeAge(JNIEnv *env, jclass clazz) {
// TODO: implement changeAge()
// 静态函数不存在this
// jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig)
jfieldID ageFid = env->GetStaticFieldID(clazz, "age", "I");
// jint value = 29;
// void SetIntField(jobject obj, jfieldID fieldID, jint value)
env->SetStaticIntField(clazz,ageFid, env->GetStaticIntField(clazz, ageFid) + 1);
// int result = env->GetStaticIntField(clazz, ageFid);
//
// LOGD("result:%i\n", result);
}
二、数据类型映射
Java 和 C/C++ 的数据类型存在差异,在 JNI 中需要进行相应的映射,以确保数据能够正确传递。
1. 基本数据类型映射
| Java 类型 | JNI 类型 | C/C++ 类型 |
| boolean | jboolean | unsigned char |
| byte | jbyte | signed char |
| char | jchar | unsigned short |
| short | jshort | short |
| int | int | int |
| long | jlong | long long |
| float | jfloat | float |
| double | jdouble | double |
2.引用类型映射
Java 的引用类型(如类、对象、数组等)在 JNI 中也有对应的类型:
- jobject:对应 Java 中的 Object 类型。
- jclass:对应 Java 中的 Class 类型。
- jstring:对应 Java 中的 String 类型。
- jarray:对应 Java 中的数组类型,此外还有jintArray、jbyteArray等具体数组类型。
三、函数签名
在 JNI 编程中,函数签名是 Java 与 C/C++ 代码交互的 “暗号”,用于唯一标识方法的参数类型和返回值类型。当从 C/C++ 调用 Java 方法时(如通过GetMethodID获取方法 ID),必须提供正确的函数签名。
签名规则:
- 基础类型:如int对应I,long对应J
- 引用类型:以L开头,以;结尾,中间为完整类路径,如String对应Ljava/lang/String;
- 数组类型:在类型前加[,如int[]对应[I,二维数组long[][]对应[[J
- 函数签名的格式为:(参数类型列表)返回值类型
基本类型和引用类型的表示规则如下:
| Java 类型 | 签名表示 | 示例 |
|---|---|---|
| boolean | Z | |
| byte | B | |
| char | C | |
| short | S | |
| int | I | |
| long | J | |
| float | F | |
| double | D | |
| void | V | |
| 引用类型(类) | L 全类名; | String → Ljava/lang/String; |
| 数组类型 | [元素类型; | int[] → [I;String[] → [Ljava/lang/String; |
四、开发流程
- 设置开发环境:在 Android Studio 中安装 Android NDK,并配置好相关开发环境。
- 创建或导入项目:在 Android Studio 中创建一个新的 Android Native 项目,或者将现有的 C/C++ 代码导入到一个新的Android项目中。
- 编写 C/C++ 代码:在项目中编写 C/C++ 代码,实现你的功能和逻辑。
- 配置构建脚本:使用 CMake 配置构建脚本,指定 C/C++ 代码的编译选项和依赖库
// app/build.gradle
externalNativeBuild {
cmake { // CMake 构建系统
path file('src/main/cpp/CMakeLists.txt') // 指定 CMakeLists.txt 文件, 该文件定义了如何构建项目中的 C++ 代码
version '3.22.1'
}
}
5. 编译和运行:通过 Android Studio 的构建工具编译你的项目,并在模拟器或实际设备上运行。
1420

被折叠的 条评论
为什么被折叠?



