Bootstrap

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++ 数据类型描述
booleanjbooleanunsigned char无符号 8 位整数
bytejbytesigned char有符号 8 位整数
charjcharunsigned short无符号 16 位整数
shortjshortshort有符号 16 位整数
intjintint有符号 32 位整数
longjlonglong long有符号 64 位整数
floatjfloatfloat32 位浮点数
doublejdoubledouble64 位浮点数

红色部分数据类型,在转换时可以直接使用。比如

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 签名
ObjectL全限定类名;
StringLjava/lang/String;
ClassLjava/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 类型描述符
intI
longJ
floatF
doubleD
booleanZ
charC
shortS
byteB
voidV
StringLjava/lang/String;
int[][I
String[][Ljava/lang/String;
int[][][[I
ObjectLjava/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开发实战测试

学习资料分享

0voice · GitHub

;