Android NDK开发入门3之基本语法
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后面是cdataenv->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; //构造方法IDjmethodID jmethodID_Method_Instance; //方法的IDjobject jobjectMyClass; //类的实例//1、从classpath路径下搜索ClassMethod这个类,并返回该类的Class对象mJclass = env->FindClass("com/example/first_ndk_cpp/ClassField");if (mJclass == NULL) {printf("====FindClass not found \n");return;}// 2、获取类的默认构造方法IDjmethodID_Construct = env->GetMethodID(mJclass, "<init>", "()V");if (jmethodID_Construct == NULL) {printf("GetMethodID not found ==>>jmethodID_Construct");return;}// 3、查找实例方法的IDjmethodID_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的属性IDfid = env->GetFieldID(clazz, "str2" //变量名称, "Ljava/lang/String;" //变量类型签名);if (clazz == NULL) {return;}// 3. 获取实例变量str的值j_str = static_cast<jstring>(env->GetObjectField(obj, fid)); //JNIEnv*, jobject, jfieldIDif (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*, jobjectenv->DeleteLocalRef(j_str); //JNIEnv*, jobjectenv->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的属性IDfid = 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开发实战测试
学习资料分享
0voice · GitHub
相关文章:
Android NDK开发入门3之基本语法
JNI语法基础 函数生成语法: extern “ C” 作⽤:避免编绎器按照C的⽅式去编绎C函数 1、C不⽀持函数的重载,编译之后函数名不变; 2、C⽀持函数的重载(这点与Java⼀致),编译之后函数名会改变…...
unity学习9:unity的Asset 导入和导出
目录 1 Assets 资产/资源 1.1 编辑器里Assets 和explorer文件夹 里一一对应 1.2 在编辑器里操作,和文件夹内操作,多数相同还是有些不同 2 往Assets里导入零散文件 2.1 往Assets里导入零散文件 2.2 把fbx文件导入到hierarcy /scene 里,…...
Unity学习笔记(七)使用状态机重构角色攻击
前言 本文为Udemy课程The Ultimate Guide to Creating an RPG Game in Unity学习笔记 攻击状态重构 首先我们重构攻击状态的动画 之前的动画,我们是使用状态(isAttacking)攻击次数(comboCounter)完成动画的过渡,这样虽然能完成功能,但是如…...
【整理集合大全】MySQL(4) 数据库增删改查SQL语句
查看数据库 show databases; 使用数据库 use 数据库名;创建数据库 CREATE DATABASE 数据库名;删除数据库 DROP DATABASE 数据库名;创建表 create table 表名(列名1 类型(长度) [约束],列名2 类型(长度) [约束],…… );长度区别 int类型带长度:不影响存取值&…...
Flutter 鸿蒙化 flutter和鸿蒙next混和渲染
前言导读 这一个节课我们讲一下PlatformView的是使用 我们在实战中有可能出现了在鸿蒙next只加载一部分Flutter的情况 我们今天就讲一下这种情况具体实现要使用到我们的PlatformView 效果图 具体实现: 一、Native侧 使用 DevEco Studio工具打开 platform_view_example\oho…...
Flask返回浏览器无乱码方法
# -*- coding: utf-8 -*- from flask import Flask, request, jsonify, Response import os import json import re from datetime import datetime import logging import sys import crawling_web_knowledgeapp Flask(__name__)app.json.ensure_ascii False # 解决中文乱码…...
Tauri教程-基础篇-第二节 Tauri的核心概念下篇
“如果结果不如你所愿,就在尘埃落定前奋力一搏。”——《夏目友人帐》 “有些事不是看到了希望才去坚持,而是因为坚持才会看到希望。”——《十宗罪》 “维持现状意味着空耗你的努力和生命。”——纪伯伦 Tauri 技术教程 * 第四章 Tauri的基础教程 第二节…...
直播预告|StarRocks 3.4,打造 AI 时代的智能数据基座,应用场景全面扩展
随着新年的到来,StarRocks 3.4 即将上线,为 AI Workload 和更多应用场景提供强大支持!此次升级聚焦于提升 AI 场景支持,并扩展更多应用场景,全方位提升数据分析体验。 更强的 AI 场景支持: 引入 Vector In…...
Maven的基本使用
Maven apache 旗下的开源项目,是一款用于管理构建Java的项目的工具 一 作用 1依赖管理:管理jar包,避免依赖冲突 2统一项目结构 : 3项目构建: 二 安装 下面是全球唯一的中央仓库 https://repo1.maven.org/maven2…...
【深度学习入门_基础篇】线性代数本质
开坑本部分主要为基础知识复习,新开坑中,学习记录自用。 学习目标: 熟悉向量、线性组合、线性变换、基变换、矩阵运算、逆函数、秩、列空间、零空间、范式、特征指、特征向量等含义与应用。 强烈推荐此视频: 【官方双语/合集】…...
数据库模型全解析:从文档存储到搜索引擎
目录 前言1. 文档存储(Document Store)1.1 概念与特点1.2 典型应用1.3 代表性数据库 2. 图数据库(Graph DBMS)2.1 概念与特点2.2 典型应用2.3 代表性数据库 3. 原生 XML 数据库(Native XML DBMS)3.1 概念与…...
LED背光驱动芯片RT9293应用电路
一)简介: RT9293 是一款高频、异步的 Boost 升压型 LED 定电流驱动控制器,其工作原理如下: 1)基本电路结构及原理 RT9293的主要功能为上图的Q1. Boost 电路核心原理:基于电感和电容的特性实现升压功能。当…...
Ubuntu挂载云盘操作步骤
1. 查看磁盘分区情况 使用 fdisk -l 命令查看当前系统中所有磁盘的分区情况,找到需要挂载的云盘设备,例如/dev/vdc。 2. 创建新分区 使用 fdisk /dev/vdc 命令对云盘进行分区操作: 输入n创建新分区。 输入p选择创建主分区。 输入1指定分区…...
【中间件】docker+kafka单节点部署---zookeeper模式
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言消息中间件介绍1. KRaft模式2. zookeeper模式2.1. 单节点部署安装验证 前言 最近生产环境上准备部署ELFK日志监控,先在测试环境部署单节点kafka验证…...
Arduino IDE刷微控制器并下载对应固件的原由
在使用Arduino IDE刷写某个微控制器时,下载对应的固件通常是为了确保微控制器能够正确识别和执行Arduino IDE中编写的代码。以下是对这一过程的详细解释: 一、固件的作用 固件是微控制器或嵌入式设备上运行的软件,它负责控制硬件设备的操作…...
深兰科技董事长陈海波应邀为华东师大心理学专业师生做AI专题讲座
12月28日,应上海华东师范大学的邀请,上海市科协常委、上海交通大学博士生导师、深兰科技创始人兼董事长陈海波专程到校,为该校心理学专业的全体师生做了一场关于人工智能推动个人数字化未来的专题讲座。 他在演讲中,首先详细讲述了…...
iOS - 引用计数(ARC)
1. 基本数据结构 // 对象结构 struct objc_object {isa_t isa; // isa 指针,包含引用计数信息 };// isa 的位域结构 union isa_t {uintptr_t bits;struct {uintptr_t nonpointer : 1; // 是否启用优化的 isa 指针uintptr_t has_assoc : 1; // 是…...
【物联网原理与运用】知识点总结(上)
目录 名词解释汇总 第一章 物联网概述 1.1物联网的基本概念及演进 1.2 物联网的内涵 1.3 物联网的特性——泛在性 1.4 物联网的基本特征与属性(五大功能域) 1.5 物联网的体系结构 1.6 物联网的关键技术 1.7 物联网的应用领域 第二章 感知与识别技术 2.1 …...
Flux“炼丹炉”——fluxgym安装教程
一、介绍 这个炼丹炉目前可以训练除了flux-dev之外的其它模型,只需更改一个配置文件内容即可。重要的是训练时不需要提前进行图片裁剪、打标等前置工作,只需下图的三个步骤即可开始训练。它就是——fluxgym。 fluxgym:用于训练具有低 VRAM &…...
【Jsoncpp】manipulating JSON data in C++
源代码 #include <iostream> // 引入输入输出流库,用于标准输入输出操作 #include <fstream> // 引入文件流库,用于文件读写操作 #include <json/json.h> // 引入JSON库,用于解析和操作JSON数据using namespace std; …...
Ardupilot开源无人机之Geek SDK进展2024
Ardupilot开源无人机之Geek SDK进展202501 1. 源由2. 状态3. TODO3.1 跟踪目标框3.2 onnxruntime版本3.3 CUDA 11.8版本3.4 pytorch v2.5.1版本3.5 Inference性能3.6 特定目标集Training 4. 参考资料 1. 源由 前期搭建《Ardupilot开源无人机之Geek SDK》,主要目的是…...
肝了半年,我整理出了这篇云计算学习路线(新手必备,从入门到精通)
大家好!我是凯哥,今天给大家分享一下云计算学习路线图。这是我按照自己最开始学习云计算的时候的学习路线,并且结合自己从业多年所涉及的知识精心总结的云计算的思维导图。这是凯哥精心总结的,花费了不少精力哦,希望对…...
配置数据的抗辐照加固方法
SRAM 型FPGA 的配置存储器可以看成是由0 和1 组成的二维阵列,帧的高度为矩阵阵列的高度,相同结构的配置帧组成配置列,如CLB 列、IOB 列、输入输出互联(Input Output Interconnect,IOI)列、全局时钟(Global Clock, GCLK)列、BRAM 列和BRAM 互联…...
【linux系统之redis6】处理可视化工具无法连接服务器端的redis
redis跑在虚拟机上的linux系统是可以正常的,但是用宿主机的可视化工具链接就连不上 可视化工具无法连接 问题排查 确保配置文件开启了bind 0.0.0.0,允许外部任何终端的链接密码确保正确要关闭linux系统的防火墙(我的属于这种) systemctl stop firewalld.servic…...
云计算操作系统的核心-OpenStack框架全解析
文章目录 一、OpenStack简介二、OpenStack架构1.认证服务Keystone2.镜像服务Glance3.计算服务Nova4.块存储服务Clinder5.对象存储服务Swift6.网络服务Neutron7.计量服务Ceilometer 三、服务简介3.1 OpenStack界面管理3.2 Keystone 认证3.3 Glance3.3 Nova3.4 存储服务3.5 Neutr…...
【设计模式-2】23 种设计模式的分类和功能
在软件工程领域,设计模式是解决常见设计问题的经典方案。1994 年,Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides(四人帮,GoF)在《设计模式:可复用面向对象软件的基础》一书中系统性地总结了…...
记录一下Coding一直不能clone
配置 下载git客户端,进行配置 git config --list user.name姓名全称 user.emailIAM_xxxxxx.com ,这个就是你的邮箱地址 user.signingkey 注册coding平台的密码 一般不需要配置公钥私钥 下载TortoiseGit,配置这几个参数 配置凭据管理器 注意 这里用户名是…...
Backend - C# EF Core 执行迁移 Migrate
目录 一、创建Postgre数据库 二、安装包 (一)查看是否存在该安装包 (二)安装所需包 三、执行迁移命令 1. 作用 2. 操作位置 3. 执行(针对visual studio) 查看迁移功能的常用命令: 查看…...
检索增强生成 和思维链 结合: 如何创建检索增强思维链 (RAT)?
论文地址:https://arxiv.org/pdf/2403.05313 Github地址:https://github.com/CraftJarvis/RAT 想象一下,一个人工智能助手可以像莎士比亚一样写作,像专家一样推理。这听起来很了不起,对吧?但是࿰…...
第四届电子信息与通信工程国际学术会议(EICE 2025)
第四届电子信息与通信工程国际学术会议(EICE 2025)定于2025年1月10日至12日在中国广州举行。大会交流全球相关领域科技学术最新发展趋势,链接重点领域国内外顶尖、活跃、最新学术资源,通过经验分享和智慧碰撞,推动科研…...
Hbuilder ios 离线打包sdk版本4.36,HbuilderX 4.36生成打包资源 问题记录
1、打包文档地址https://nativesupport.dcloud.net.cn/AppDocs/usesdk/ios.html#%E9%85%8D%E7%BD%AE%E5%BA%94%E7%94%A8%E7%89%88%E6%9C%AC%E5%8F%B7 2、配置应用图标 如果没有appicon文件,此时找到 Assets.xcassets 或者 Images.xcassets(看你sdk引入的启动文件中…...
HarmonyOS开发:传参方式
一、父子组件传参 1、父传子(Prop方式) 父组件代码 Entry Component struct ParentComponent {State parentMessage: string Hello from Parent;build() {Column() {ChildComponent({ message: this.parentMessage });}} } 子组件代码 Component s…...
Tomcat(116) 如何在Tomcat中解决缓存问题?
在Tomcat中解决缓存问题涉及多个方面的优化和处理,包括优化缓存配置、监控缓存状态、处理缓存失效和一致性问题等。以下是详细的步骤和代码示例,帮助你在Tomcat中解决缓存问题。 1. 优化缓存配置 Tomcat内置了一些静态资源的缓存机制,可以通…...
2.STM32F407ZGT6-外部中断
参考: 1.正点原子。 前言: MCU最重要的一个领域–中断。总结下嵌套向量和外部中断的概念。达到: 1.NVIC是什么,了解中断的整体管理理念。 2.中断里面最简单的外部中断,怎么配置处理。 3.使用STM32CubeMX配置外部中断的…...
C# 之某度协议登录,JS逆向,手机号绑定,获取CK
.NET兼职社区 .NET兼职社区 .NET兼职社区 .NET兼职社区 有需要指导,请私信我留言V或者去社区找客服。...
0基础学前端-----CSS DAY12
视频参考:B站Pink老师 今天是CSS学习的第十二天,今天开始的笔记对应Pink老师课程中的CSS第七天的内容。 本节重点:CSS高级技巧 本章目录 本节目标1. 精灵图1.1 为什么需要精灵图1.2 精灵图使用案例:拼出自己的名字 2. 字体图标2.…...
STM32——系统滴答定时器(SysTick寄存器详解)
文章目录 1.SysTick简介2.工作原理3.SysTick寄存器4.代码延时逻辑5.附上整体代码6.一些重要解释 1.SysTick简介 Cortex-M处理器内集成了一个小型的名为SysTick(系统节拍)的定时器,它属于NVIC的一部分,且可以产生 SysTick异常(异常类型#15)。SysTick为简单的向下计数的24位计数…...
HTML5 弹跳动画(Bounce Animation)详解
HTML5 弹跳动画(Bounce Animation)详解 弹跳动画是一种动态效果,使元素在出现或消失时看起来像是在跳动。这种效果可以通过 CSS 动画或 JavaScript 来实现,增强用户体验。 1. 使用 CSS 实现弹跳动画 可以使用 CSS 的 keyframes…...
Python 模拟登录网页,或者编写爬虫时模拟登录的详细总结
参考 Python模拟登陆网页的三种方法_python模拟登录-CSDN博客 python-模拟登陆多种方法总结_python模拟登录-CSDN博客 Python模拟登录的几种方法_实现模拟登录的方式有哪些?-CSDN博客 Python爬虫——模拟登录_python 模拟登录-CSDN博客 Python3 爬虫 模拟登录_python模拟登录网…...
若依框架简介
若依(RuoYi)框架是一个基于Java语言的后台管理系统快速开发框架,它结合了多种前端和后端技术,提供了高效的开发工具。以下是对若依框架的详细解析: 一、技术架构 后端:若依框架后端采用了Spring Boot、My…...
Linux中rsync命令使用
一、rsync简介 rsync 是一种高效的文件复制和同步工具,常用于在本地或远程计算机之间同步文件和目录 主要特性增量同步:rsync 会检测源和目标文件之间的差异,只传输发生变化的部分,而不是重新传输整个文件。这样就能有效减少数据…...
opencv 学习(1)
文章目录 opencv导学部分opencv的作用ffmpeg和 opencv的关系opencv的未来 计算机视觉是什么? opencv导学部分 opencv的作用 1 : 目标识别 人脸识别 车辆识别 2 : 自动驾驶技术 – 计算机视觉 进行车道的检测 3 : 医学图像分析 通过分析光片 来分析人到底得了什么病…...
中高级运维工程师运维面试题(十一)之 Docker
目录 往期回顾前言基础知识1. 什么是 Docker?2. Docker 的核心组件有哪些?3. Docker 镜像和容器有什么区别?4. 什么是 Dockerfile? 高级知识5. 什么是多阶段构建?如何使用?6. Docker 网络有哪些模式&#x…...
Kafka如何实现顺序消费?
Kafka的消息是存储在指定的topic中的某个partition中的。并且一个topic是可以有多个partition的。同一个partition中的消息是有序的,但是跨partition,或者跨topic的消息就是无序的了。 为什么同一个partition的消息是有序的? 因为当生产者向某个parti…...
通过 ulimit 和 sysctl 调整Linux系统性能
目录 一:资源限制管理工具:ulimit1、ulimit 作用介绍2、ulimit 常用选项3、ulimit 配置文件 二:内核参数调整工具:sysctl1、sysctl 作用介绍2、sysctl 常用选项3、sysctl 配置文件 一:资源限制管理工具:uli…...
pandas基础使用
pandas基础使用 基本介绍 类似于字典形式的numpy,可以给它的不同行和不同列进行重命名。 import numpy as np import pandas as pd # 创建一个序列 s pd.Series([1,4,True,np.nan,55.0])创建date format日期矩阵 import numpy as np import pandas as pd dates…...
2024.1.5总结
今日不开心:这周本来想花点时间学习的,没想到全都花在刷视频,外出消费去了。 今日思考: 1.找对象这件事确实不能强求,顺其自然吧,单身和不单身,其实,各有各的利弊。在一次坐地铁的过程中,我一…...
解释一下:运放的输入偏置电流
输入偏置电流 首先看基础部分:这就是同相比例放大器 按照理论计算,输入VIN=0时,输出VOUT应为0,对吧 仿真与理论差距较大,有200多毫伏的偏差,这就是输入偏置电流IBIAS引起的,接着看它的定义 同向和反向输入电流的平均值,也就是Ib1、Ib2求平均,即(Ib1+Ib2)/2 按照下面…...
Federation机制的实现
1.关闭Hadoop的HDFS和YARN,依次执行“stop-yarn.sh”和“stop-dfs.sh”命令关闭Hadoop的YARN和HDFS。 2.删除3台虚拟机上的临时文件: 3.修改hdfs-site.xml配置文件,进入虚拟机liumengting1的/export/servers/hadoop-3.3.4/etc/hadoop目录&…...
120.Jenkins里的Pipeline Script
目录 1. **Declarative Pipeline** 主要部分 示例 2. **Scripted Pipeline** 主要部分 示例 3. **常用指令和功能** 环境变量 工具管理 文件操作 构建触发器 并行执行 异常处理 用户交互 4.**两种类型的特点** 1. **声明式 Pipeline (Declarative Pipeline)** 中…...