Android NDK开发入门3之基本语法

发布于:2025-02-10 ⋅ 阅读:(59) ⋅ 点赞:(0)

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开发实战测试

学习资料分享

0voice · GitHub