JNI语法基础
函数生成语法:
extern “ C” 作⽤:避免编绎器按照C++的⽅式去编绎C函数
1、C不⽀持函数的重载,编译之后函数名不变;
2、C++⽀持函数的重载(这点与Java⼀致),编译之后函数名会改变;
JNIEXPORT :⽤来表示该函数是否可导出(即:⽅法的可⻅性)
JNICALL :⽤来表示函数的调⽤规范(如:__stdcall)
举例:
//extern "C" C语言编译方式 默认是C++
//JNIEXPORT 相当于public:
//jstring 数据类型
//JNICALL 函数的调⽤规范
extern "C" JNIEXPORT jstring JNICALL
//stringFromJNI是c++function名称
Java_com_example_first_1ndk_1cpp_MainActivity_stringFromJNI
JNIEnv、jobject与jclass详解
JNIEnv,顾名思义,指代了Java本地接⼝环境(Java Native Interface Environment),是⼀个JNI 接⼝指针,指向了本地⽅法的⼀个函数表,该函数表中的每⼀个成员指向了⼀个JNI函数,本地⽅法通过 JNI函数来访问JVM中的数据结构,详情如下图:
jobject
:调用该方法的 Java 对象实例(对于实例方法)或类对象(对于静态方法)。
jclass
是 Java 类的本地表示。在 JNI 中,jclass
用于表示一个 Java 类的引用。
应用:当ndk定义c++静态函数的时候,参数则是jclass,因为c++静态函数属于类。而成员函数则属于对象 那ndk则是jobject。
java声明:
public native String setName(String name);
static public native String getVersion();
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_first_1ndk_1cpp_MainActivity_setName
(JNIEnv *env,
jobject
, jstring name ){
const char * data = env->GetStringUTFChars(name, nullptr);
std::string str = "relust" + std::string(data);
if(!data)
return NULL;
//释放name内存
env->ReleaseStringChars(name,reinterpret_cast<const jchar*>(data));
return env->NewStringUTF(str.data());
}
extern "C" JNIEXPORT jstring JNICALL Java_com_example_first_1ndk_1cpp_MainActivity_getVersion
(JNIEnv *env,
jclass){
const char * str ="static v3.0";
return env->NewStringUTF(str);
}
C与C++中的JNIEnv区别
本质来说JNIEnv是一个指针,通过指针调用实现java操作。而c语言因为没用引用,则需要进行双重指针指向调用:(*env)->getStringUTF()。
c++是引用机制,那调用可以通过引用的方法:env->getStringUTF()。
举例改成.c版本:
#include <jni.h>
JNIEXPORT jstring JNICALL
Java_com_darren_ndk_14_13_MainActivity_stringFromJNI(
JNIEnv *env,
jobject obj /* this */) {
char str[] = "Hello from C";
return (*env)->NewStringUTF(env, str);
}
/*
* Class: com_darren_ndk_4_3_MainActivity
* Method: staticStringFromJNI
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL
Java_com_darren_ndk_14_13_MainActivity_staticStringFromJNI
(JNIEnv *env, jclass jcs)
{
char str[] = "Static Hello from C";
return (*env)->NewStringUTF(env, str);
}
如何根据native 接⼝⽣成jni接⼝
JNI接⼝命名规则:Java_<全限定类名>_<方法名>
Java_
:固定前缀,表示这是一个 JNI 方法。
<全限定类名>
:Java 类的全限定名(包名 + 类名),其中包名中的点(.
)替换为下划线(_
)。
<方法名>
:Java 中的方法名。package com.example; public class HelloJNI { public native void sayHello(); } 对应的 JNI 方法名为: Java_com_example_HelloJNI_sayHello
为了方便写头文件可以采取javah.exe程序,通常使用脚本方式编写。
ndk脚本生成
-classpath . -jni -d $SourcepathEntry$ $FileClass$
$SourcepathEntry$
生成了对应的头文件,我们直接copy使用即可
public native String setName(String name);
----》
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_first_1ndk_1cpp_MainActivity_setName
(JNIEnv *env, jobject, jstring name ){
const char * data = env->GetStringUTFChars(name, nullptr);
std::string str = "relust" + std::string(data);
if(!data)
return NULL;
//释放name内存
env->ReleaseStringChars(name,reinterpret_cast<const jchar*>(data));
return env->NewStringUTF(str.data());
}
基本数据类型
JNI 定义了与 Java 数据类型对应的本地类型且与c++基本数据类型类似:
Java 类型 | JNI 类型 | C++ 数据类型 | 描述 |
---|---|---|---|
boolean | jboolean | unsigned char | 无符号 8 位整数 |
byte | jbyte | signed char | 有符号 8 位整数 |
char | jchar | unsigned short | 无符号 16 位整数 |
short | jshort | short | 有符号 16 位整数 |
int | jint | int | 有符号 32 位整数 |
long | jlong | long long | 有符号 64 位整数 |
float | jfloat | float | 32 位浮点数 |
double | jdouble | double | 64 位浮点数 |
红色部分数据类型,在转换时可以直接使用。比如
java层传递调用:
short s = 1;int i = 10;long l = 100; ...
JniClient.TestDataTypeJ2C(s, i, l, f, d, c, z, b, str, array, obj, mMyJavaClass);
JNIEXPORT void JNICALL Java_com_example_first_1ndk_1cpp_JniClient_TestDataTypeJ2C(
JNIEnv *env, jclass mJclass, jshort mJshort, jint mJint, jlong mJlong,
jfloat mJfloat, jdouble mJdouble, jchar mJchar, jboolean mJboolean,
jbyte mJbyte, jstring mJstring, jintArray mJintArray, jobject mJobject,
jobject mJobjectClass) {
// %d 有符号10进制整数%ld 长整型%hd短整型 %md,m指定的是输出字段的宽度
short s = mJshort;
LOGI("mJshort==>%hd\n", s);
LOGI("mJint==>%d\n", mJint);
LOGI("mJlong==>%ld\n", mJlong);
LOGI("mJfloat==>%f\n", mJfloat);
LOGI("mJdouble==>%lf\n", mJdouble);
LOGI("mJchar==>%c\n", mJchar);
LOGI("mJboolean==>%d\n", mJboolean);
LOGI("mJbyte==>%d\n", mJbyte);
//基本数据类型
//jclass mJclass, jshort mJshort, jint mJint, jlong mJlong, jfloat mJfloat,
//jdouble mJdouble, jchar mJchar, jboolean mJboolean, jbyte mJbyte
}
引用类型签名
引用类不能直接使用,需要进行转换
JNI引⽤类型也存在⼀个继承关系,当我们在进⾏JNI实际开发的时候,可以参照 进⾏相对应的转换:
Java 类型 | JNI 签名 |
---|---|
Object | L全限定类名; |
String | Ljava/lang/String; |
Class | Ljava/lang/Class; |
任意类型数组 | [类型] |
jstring示例
java层:ndk调用返回java string
String strFromC = JniClient.AddStr("Java2C_参数1", "Java2C_参数2");
JNIEXPORT jstring JNICALL Java_com_example_first_1ndk_1cpp_JniClient_AddStr
(JNIEnv *env, jclass arg, jstring instringA, jstring instringB)
{
jstring str = env->NewStringUTF("I am from JNI");
const char *str1 = env->GetStringUTFChars(instringA, NULL);
const char *str2 = env->GetStringUTFChars(instringB, NULL);
int size = strlen(str1) + strlen(str2);
char *n_str_point = (char *) malloc(size + 1);
strcpy(n_str_point, str1); //将字符串str1拷贝到字符数组
strcat(n_str_point, str2); //函数原形char *strcat(char *dest, const char *src);
jstring result = env->NewStringUTF(n_str_point);
//资源回收
free(n_str_point); //注意只能释放动态分配的内存
env->ReleaseStringUTFChars(instringA, str1); //前面是jdata后面是cdata
env->ReleaseStringUTFChars(instringB, str2);
return result; //需要转换为中间层jstring返回
}
int数组应用
java层调用:
int[] javaArray = new int[]{10, 20, 30, 40, 50, 60};
// 底层相加输出结果,并返回数组
int[] javaArrayResult = JniClient.sumArray(javaArray);
JNIEXPORT jintArray JNICALL Java_com_example_first_1ndk_1cpp_JniClient_sumArray(
JNIEnv *env, jclass mJclass, jintArray mjintArray)
{
jint cSum = 0;
jint cLen = 0;
//1.获取数组长度
cLen = env->GetArrayLength(mjintArray);
//2.根据数组长度和数组元素的数据类型申请存放java数组元素的缓冲区
//方法1: jarray转换为c语言可操作的数组
// jint* cPArray = (jint*) malloc(sizeof(jint) * cLen);
// if (cPArray == NULL)
// return NULL; //可能申请内存空间失败
// //3.初始化内存区 http://www.jianshu.com/p/cb8a3c004563
// memset(cPArray, 0, sizeof(jint) * cLen);
// LOGI("cLen==length>%d\n", cLen);
// LOGI("cLen==sizeof>%d\n", sizeof(jint)*cLen);
// //4. 拷贝Java数组中的所有元素到缓冲区中
// env->GetIntArrayRegion(mjintArray, 0, cLen, cPArray); //得到数组方式1 把数据放在缓冲区
//free(cPArray );
// 方法2 jnk 获取数组元素的指针
jint *cPArray = env->GetIntArrayElements(mjintArray, NULL);
if (cPArray == NULL) {
return 0; // JVM复制原始数据到缓冲区失败
}
// 3. 求数组和
jint i;
for (i = 0; i < cLen; i++) { //求数组和
cSum = cSum + cPArray[i];
}
LOGI("jSum==>%d\n", cSum);
// 4. 创建一个新的 Java int数组来存放结果
//给java层返回数组方式1
jint cInts[cLen]; //定义一个数组
for (i = 0; i < cLen; i++) {
cInts[i] = cPArray[i];
}
jintArray result;
result = env->NewIntArray(cLen); //申请数组clen个
if (result == NULL) {
env->ReleaseIntArrayElements(mjintArray, cPArray, 0);
return NULL; /* out of memory error thrown */
}
// 将native数组转换为java层数组
//length = sizeof(array) / sizeof(array[0]); c/c++中求数组长度
env->SetIntArrayRegion(result, 0, sizeof(cInts) / sizeof(cInts[0]),
cInts);
//如果不借助cInts作为temp,可以直接这样。
// env->SetIntArrayRegion(result, 0, cLen, cPArray);
env->ReleaseIntArrayElements(mjintArray, cPArray, 0); // 释放可能复制的缓冲区
return result;
}
如何从c++层返回java层一个二维数组
int[][] java2ArrayResult = JniClient.getArrayObjectFromC(100);
Toast.makeText(MainActivity.this, "native中返回对象数组" +
java2ArrayResult[0][0] + "===" + java2ArrayResult[1][1]
, Toast.LENGTH_SHORT).show();
JNIEXPORT jobjectArray JNICALL Java_com_example_first_1ndk_1cpp_JniClient_getArrayObjectFromC
(JNIEnv *env, jclass mJclass, jint mJlen) {
// 1. 获取一维整型数组的类引用
jobjectArray jObjectArrayResult; // 用于存储二维数组
jclass jClassArray;
jClassArray = env->FindClass("[I"); // "[I" 表示一维整型数组
if (jClassArray == NULL) {
return NULL; // 如果获取失败,返回NULL
}
// 2. 创建一个二维数组对象
jObjectArrayResult = env->NewObjectArray(mJlen, jClassArray, NULL);
if (jObjectArrayResult == NULL) {
return NULL; // 如果创建失败,返回NULL
}
// 3. 为二维数组的每个元素赋值
for (jint i = 0; i < mJlen; i++) {
// 3.1 创建一个一维数组
jintArray mJintArray = env->NewIntArray(mJlen); // 分配长度为 mJlen 的一维数组
if (mJintArray == NULL) {
return NULL; // 如果创建失败,返回NULL
}
// 3.2 创建一个本地缓冲区数组
jint mBuff[mJlen];
for (jint j = 0; j < mJlen; j++) {
mBuff[j] = i + j; // 计算每个元素的值
}
// 3.3 将本地缓冲区的数据复制到一维数组中
env->SetIntArrayRegion(mJintArray, 0, mJlen, mBuff);
// 3.4 将一维数组设置到二维数组的第 i 个位置
env->SetObjectArrayElement(jObjectArrayResult, i, mJintArray);
// 3.5 释放一维数组的本地引用,避免内存泄漏
env->DeleteLocalRef(mJintArray);
}
// 4. 返回二维数组
return jObjectArrayResult;
}
类描述符
在JNI(Java Native Interface)中,类描述符是用来标识Java类的字符串。它用于在C/C++代码中引用Java类或对象。类描述符的格式遵循特定的规则,通常以 L
开头,以 ;
结尾,中间是类的全限定名(包名 + 类名),并用 /
代替 .
。
常见类型的类描述符
Java 类型 | JNI 类型描述符 |
---|---|
int | I |
long | J |
float | F |
double | D |
boolean | Z |
char | C |
short | S |
byte | B |
void | V |
String | Ljava/lang/String; |
int[] | [I |
String[] | [Ljava/lang/String; |
int[][] | [[I |
Object | Ljava/lang/Object; |
⽅法描述符
在 JNI(Java Native Interface)中,方法描述符(Method Descriptor)是用来描述 Java 方法的参数类型和返回值类型的字符串。它用于在 C/C++ 代码中准确地识别和调用 Java 方法。方法描述符的格式遵循特定的规则,通常包括参数类型和返回值类型。
方法描述符的格式为:
(参数类型)返回值类型
示例
public void methodName() {} -》()V
public void methodName(int num) {} -》(I)V
public int methodName(String str, int num) {} -》(Ljava/lang/String;I)I
描述符的使用场景
静态调用function java -> ndk -> jni
java层ndk调用静态cpp api,cpp api通过jni调用静态java api。
java层ndk调用
JniClient.callJavaStaticMethod();
JNIEXPORT void JNICALL Java_com_example_first_1ndk_1cpp_JniClient_callJavaStaticMethod
(JNIEnv *env, jclass cla)
{
jclass mJclass;
jstring mJstring;
jmethodID mJStaticmethodID;
int i = 0;
//1.从classpath路径下搜索ClassMethod这个类,并返回该类的Class对象
mJclass = env->FindClass("com/example/first_ndk_cpp/ClassField"); //JNIEnv*, const char*
if (mJclass == NULL) {
//LOGI("callJavaStaticMethod==>>mJclass==NULL==>>%s", "");
return;
}
// 2、从clazz类中查找callStaticMethod方法
mJStaticmethodID = env->GetStaticMethodID(
mJclass, //类
"StaticcallStaticMethod", //api名称
"(Ljava/lang/String;I)V" //类描述符 函数签名
);
if (mJStaticmethodID == NULL) {
printf("=====>>>can not foud callStaticMethod");
return;
}
//3、调用clazz类的callStaticMethod静态方法
mJstring = env->NewStringUTF(" == C++== "); // 可以修改这里的字符串来验证
env->CallStaticVoidMethod(mJclass, mJStaticmethodID
, mJstring, //两个参数
200); //JNIEnv*, jclass, jmethodID, ...
// 删除局部引用
env->DeleteLocalRef(mJstring);
env->DeleteLocalRef(mJclass);
}
package com.example.first_ndk_cpp;
public class MyJavaClass {
private static void callStaticMethod(String str, int i) {
resultFromC = str + "====" + i;//native 调用此方法 回传参数
System.out.format("ClassMethod::callStaticMethod called!-->str=%s,"
+ " i=%d\n", str, i);
}
}
对象实例调用function java -> ndk -> jni
因为是对象实例,所以需要构造出对象,通过对象调用。
java层:
JniClient.callJavaInstaceMethod();
ndk:
JNIEXPORT void JNICALL Java_com_example_first_1ndk_1cpp_JniClient_callJavaInstaceMethod
(JNIEnv *env, jclass jclazz)
{
jclass mJclass = NULL;
jmethodID jmethodID_Construct; //构造方法ID
jmethodID jmethodID_Method_Instance; //方法的ID
jobject jobjectMyClass; //类的实例
//1、从classpath路径下搜索ClassMethod这个类,并返回该类的Class对象
mJclass = env->FindClass("com/example/first_ndk_cpp/ClassField");
if (mJclass == NULL) {
printf("====FindClass not found \n");
return;
}
// 2、获取类的默认构造方法ID
jmethodID_Construct = env->GetMethodID(mJclass, "<init>", "()V");
if (jmethodID_Construct == NULL) {
printf("GetMethodID not found ==>>jmethodID_Construct");
return;
}
// 3、查找实例方法的ID
jmethodID_Method_Instance = env->GetMethodID(
mJclass,
"callInstanceMethod"
, "(Ljava/lang/String;I)V");
if (jmethodID_Method_Instance == NULL) {
return;
}
// 4、创建该类的实例 调用无参构造方法 如果其它构造方法,后面可传参数
jobjectMyClass = env->NewObject(mJclass, jmethodID_Construct); //JNIEnv*, jclass, jmethodID, ...
if (jobjectMyClass == NULL) {
printf(
"在com.example.testndkeclipse.MyJavaClass 类中找不到callInstanceMethod方法");
return;
}
// 5、调用对象的实例方法
jstring mJstring = env->NewStringUTF("==I am from Native==");
env->CallVoidMethod(jobjectMyClass, jmethodID_Method_Instance,
mJstring, 201); //JNIEnv*, jobject, jmethodID, ...
//或者jni调用:
//jstring mJstring = env->NewStringUTF(env,"==我来自Native,通过调用java对象方法传递==");
// 删除局部引用
env->DeleteLocalRef(mJstring);
env->DeleteLocalRef(jobjectMyClass);
// env->DeleteLocalRef(jmethodID_Method_Instance);
// env->DeleteLocalRef(jmethodID_Construct);
}
package com.example.first_ndk_cpp;
public class MyJavaClass {
private static void callStaticMethod(String str, int i) {
resultFromC = str + "====" + i;//native 调用此方法 回传参数
System.out.format("ClassMethod::callStaticMethod called!-->str=%s,"
+ " i=%d\n", str, i);
}
}
C++修改java层的类成员属性
java层:
ClassField obj = new ClassField(10,"Hello");
JniClient.accessInstanceField(obj);
JNIEXPORT void JNICALL Java_com_example_first_1ndk_1cpp_JniClient_accessInstanceField
(JNIEnv *env, jclass jcl, jobject obj)
{
jclass clazz;
jfieldID fid;
jstring j_str;
jstring j_newStr;
const char *c_str = NULL;
查 取java层传入对象的成员
// 1.获取AccessField类的Class引用
clazz = env->GetObjectClass(obj);
if (clazz == NULL) {
return;
}
// 2. 获取AccessField类实例变量str的属性ID
fid = env->GetFieldID(clazz, "str2" //变量名称
, "Ljava/lang/String;" //变量类型签名
);
if (clazz == NULL) {
return;
}
// 3. 获取实例变量str的值
j_str = static_cast<jstring>(env->GetObjectField(obj, fid)); //JNIEnv*, jobject, jfieldID
if (j_str == NULL) {
return;
}
// 4. 将unicode编码的java字符串转换成C风格字符串
c_str = env->GetStringUTFChars(j_str, NULL); //JNIEnv*, jstring, jboolean*
if (c_str == NULL) {
return;
}
printf("In C--->ClassField.str = %s\n", c_str);
env->ReleaseStringUTFChars(j_str, c_str); //JNIEnv*, jstring, const char*
修改
// 5. 修改实例变量str的值
j_newStr = env->NewStringUTF("This is C String");
if (j_newStr == NULL) {
return;
}
env->SetObjectField(obj, fid, j_newStr); //JNIEnv*, jobject, jfieldID, jobject
// 6.删除局部引用
env->DeleteLocalRef(clazz); //JNIEnv*, jobject
env->DeleteLocalRef(j_str); //JNIEnv*, jobject
env->DeleteLocalRef(j_newStr); //JNIEnv*, jobject
//env->DeleteLocalRef(env,fid);//JNIEnv*, jobject 返回的非object,不能使用 DeleteLocalRef
//使用NewObject就会返回创建出来的实例的局部引用 可 DeleteLocalRef
}
C++修改java层的静态类成员属性
java层:
ClassField obj = new ClassField(10,"Hello");
JniClient.accessStaticField(obj);
JNIEXPORT void JNICALL Java_com_example_first_1ndk_1cpp_JniClient_accessStaticField
(JNIEnv *env, jclass jcl)
{
jclass clazz;
jfieldID fid;
jint num;
//1.获取ClassField类的Class引用
clazz = env->FindClass("com/example/first_ndk_cpp/ClassField");
if (clazz == NULL) { // 错误处理
return;
}
//2.获取ClassField类静态变量num的属性ID
fid = env->GetStaticFieldID(clazz, "num", "I");
if (fid == NULL) {
return;
}
// 3.获取静态变量num的值
num = env->GetStaticIntField(clazz, fid);
printf("In C--->ClassField.num = %d\n", num);
// 4.修改静态变量num的值
env->SetStaticIntField(clazz, fid, 80);
// 删除属部引用
env->DeleteLocalRef(clazz);
}
小结
JNI 调用构造方法和父类实例方法
JNIEXPORT void JNICALL Java_com_example_first_1ndk_1cpp_JniClient_callSuperInstanceMethod
(JNIEnv *env, jclass cls) {
LOGI("Java_com_example_first_1ndk_1cpp_JniClient_callSuperInstanceMethod");//可写任意参数
LOGI("%d", 10);
printf("%d", 10);
// jclass cls_cat;
// jclass cls_animal;
// jmethodID mid_cat_init;
// jmethodID mid_run;
// jmethodID mid_getName;
// jstring c_str_name;
// jobject obj_cat;
// const char *name = NULL;
//
// // 1、获取Cat类的class引用
// cls_cat = env->FindClass("com/darren/ndk_4_4/Cat");
// if (cls_cat == NULL) {
// return;
// }
// // 2、获取Cat的构造方法ID(构造方法的名统一为:<init>)
// mid_cat_init = env->GetMethodID(cls_cat, "<init>",
// "(Ljava/lang/String;)V");
// if (mid_cat_init == NULL) {
// return; // 没有找到只有一个参数为String的构造方法
// }
//
// // 3、创建一个String对象,作为构造方法的参数
// c_str_name = env->NewStringUTF("Tom Cat");
// if (c_str_name == NULL) {
// return; // 创建字符串失败(内存不够)
// }
//
// // 4、创建Cat对象的实例(调用对象的构造方法并初始化对象)
// obj_cat = env->NewObject(cls_cat, mid_cat_init, c_str_name);
// if (obj_cat == NULL) {
// return;
// }
//
// //-------------- 5、调用Cat父类Animal的run和getName方法 --------------
// cls_animal = env->FindClass("com/darren/ndk_4_4/Animal");
// if (cls_animal == NULL) {
// return;
// }
//
// // 例1: 调用父类的run方法
// mid_run = env->GetMethodID(cls_animal, "run", "()V"); // 获取父类Animal中run方法的id
// if (mid_run == NULL) {
// return;
// }
//
// // 注意:obj_cat是Cat的实例,cls_animal是Animal的Class引用,mid_run是Animal类中的方法ID
// env->CallNonvirtualVoidMethod(obj_cat, cls_animal, mid_run);
//
// // 例2:调用父类的getName方法
// // 获取父类Animal中getName方法的id
// mid_getName = env->GetMethodID(cls_animal, "getName",
// "()Ljava/lang/String;");
// if (mid_getName == NULL) {
// return;
// }
//
// c_str_name = env->CallNonvirtualObjectMethod(obj_cat, cls_animal,
// mid_getName);
// name = env->GetStringUTFChars(c_str_name, NULL);
// printf("In C: Animal Name is %s\n", name);
// LOGI("In C: Animal Name is");//可写任意参数
// // 释放从java层获取到的字符串所分配的内存
// env->ReleaseStringUTFChars(c_str_name, name);
//
// quit:
// // 删除局部引用(jobject或jobject的子类才属于引用变量),允许VM释放被局部变量所引用的资源
// env->DeleteLocalRef(cls_cat);
// env->DeleteLocalRef(cls_animal);
// env->DeleteLocalRef(c_str_name);
// env->DeleteLocalRef(obj_cat);
}
JNI静、动态注册
在 JNI(Java Native Interface)中,Native 方法可以通过 静态注册 或 动态注册 的方式与 Java 方法关联。以下是两种注册方式的详细说明和对比:
静态注册
特点
-
默认方式:JNI 默认使用静态注册。
-
命名规则:Native 方法的名称必须遵循特定的命名规则,以便 JVM 能够找到对应的 C/C++ 函数。
-
自动绑定:JVM 在加载共享库时,会自动根据命名规则绑定 Java 方法和 Native 方法。
命名规则
静态注册的 Native 方法名称必须遵循以下格式:
Java_<包名>_<类名>_<方法名>
之前举例使用的基本都是静态注册方法
优点
-
简单易用:无需额外代码,只需遵循命名规则即可。
-
自动绑定:JVM 会自动完成方法绑定。
缺点
-
命名复杂:方法名称较长,且需要严格遵循命名规则。
-
灵活性差:无法在运行时动态修改方法绑定。
动态注册
特点
-
手动绑定:开发者需要手动将 Java 方法和 Native 方法绑定。
-
灵活性高:可以在运行时动态修改方法绑定。
-
性能更好:避免了静态注册中的方法查找开销。
缺点
-
实现复杂:需要手动编写方法映射表和注册代码。
-
依赖 JNI_OnLoad:必须实现
JNI_OnLoad
函数。
实现步骤
定义方法映射表:
使用
JNINativeMethod
结构体定义 Java 方法和 Native 方法的映射关系。实现 JNI_OnLoad 函数:
在共享库加载时,JVM 会调用
JNI_OnLoad
函数,开发者可以在此函数中注册 Native 方法。注册方法:
使用
RegisterNatives
函数将方法映射表注册到 JVM 中。
示例
package com.example;
public class MyClass {
public native void nativeMethod();
public native int nativeMethod2(int a, int b);
}
C/C++ ndk代码
#include <jni.h>
#include <stdio.h>
// Native 方法实现
void nativeMethodImpl(JNIEnv *env, jobject obj) {
printf("Hello from native method!\n");
}
// Native 方法 2 的实现
jint nativeMethod2Impl(JNIEnv *env, jobject obj, jint a, jint b) {
return a + b;
}
// 方法映射表
static JNINativeMethod methods[] = {
{"nativeMethod1", "()V", (void*)nativeMethod1Impl}, // 无参数,返回 void
{"nativeMethod2", "(II)I", (void*)nativeMethod2Impl} // 两个 int 参数,返回 int
};
// JNI_OnLoad 函数
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
// 获取 Java 类的引用
jclass clazz = env->FindClass("com/example/MyClass");
if (clazz == NULL) {
return JNI_ERR;
}
// 注册 Native 方法
if (env->RegisterNatives(clazz, methods, sizeof(methods) / sizeof(methods[0])) < 0) {
return JNI_ERR;
}
return JNI_VERSION_1_6;
}
3. 静态注册 vs 动态注册
特性 | 静态注册 | 动态注册 |
---|---|---|
绑定方式 | 自动绑定 | 手动绑定 |
命名规则 | 必须遵循特定命名规则 | 无需遵循特定命名规则 |
灵活性 | 灵活性差 | 灵活性高 |
性能 | 有方法查找开销 | 无方法查找开销 |
实现复杂度 | 简单 | 复杂 |
适用场景 | 小型项目或简单 Native 方法 | 大型项目或需要动态绑定的场景 |
小结
静态注册:简单易用,但命名复杂且灵活性差。适用于小型项目或简单的 Native 方法。适合初学者,无需额外代码。
动态注册:灵活性高且性能更好,但实现复杂。适用于大型项目或需要动态绑定的场景。适合对性能要求较高的场景。
开源分享
andriod stdio ndk 学习: Android NDK开发实战测试
学习资料分享