JNI01-常用方法

创建对象

在JNI中创建Java类对应的对象,需要三步

  1. 获取jclass对象。

jclass FindClass(JNIEnv *env, const char *name);

jclass GetObjectClass(JNIEnv *env, jobject obj);
  1. 使用jclass对象调用Java类的构造函数.

所有类的构造函数的名字统一为<init>
  1. 利用jclass和构造函数创建一个jobject对象,指向的就是Java对象。

jobject NewObject(JNIEnv *env, jclass clazz, jmethodID methodID, ...);

// 还有一个分配对象的方法叫 AllocObject,但是在使用这个对象之前必须要用 CallNovirtualVoidMethod 来进行初始化,可以做到延迟初始化的效果。

举个例子:

  // 1. 获取Person对应的jlcass对象
    jclass person_clazz = env->FindClass("com/example/helllojni/Person");
    if (person_clazz == nullptr) {
        __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "Couldn't find com/example/helllojni/Person");
        return nullptr;
    }
    
    // 2. 获取构造函数的jmethodID
    jmethodID person_constructor = env->GetMethodID(person_clazz, "<init>",
                                                    "(Ljava/lang/String;I)V");
    if (person_constructor == nullptr) {
        __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "Couldn't find Person(String name, int age) constructor.");
        return nullptr;
    }
    
    // Person构造函数需要一个String参数,所以需要创建一个jstring
    jstring name = env->NewStringUTF("David");
    
    // 3. 调用构造函数创建Person对象,并返回给Java层
    return env->NewObject(person_clazz, person_constructor, name, 18);
   
  //NewObject
    jclass TestJclass = env->FindClass("com/kanxue/reflectiontest/Test");
    jmethodID con_mid = env->GetMethodID(TestJclass, "<init>", "(Ljava/lang/String;)V");
    jobject testobj2 = env->AllocObject(TestJclass);
    jstring arg1 = env->NewStringUTF("I am From Jni->AllocObject");
    env->CallNonvirtualVoidMethod(testobj2, TestJclass, con_mid, arg1);

访问对象的属性

静态属性

在 JNI 中,访问 public 与 private 的方式是一样的,get 与 set 都是对称的。

  jjclass TestJclass = env->FindClass("com/kanxue/reflectiontest/Test");
    jfieldID publicStaticField_fid = env->GetStaticFieldID(TestJclass, "publicStaticField",
                                                           "Ljava/lang/String;");
    jstring publicStaticField_obj = static_cast<jstring>(env->GetStaticObjectField(TestJclass,
                                                                                   publicStaticField_fid));
    const char *publicStaticField_content = env->GetStringUTFChars(publicStaticField_obj, nullptr);

  jfieldID privateStaticField_fid = env->GetStaticFieldID(TestJclass, "privateStaticField",
                                                            "Ljava/lang/String;");
    jstring privateStaticField_obj = static_cast<jstring>(env->GetStaticObjectField(TestJclass,
                                                                                    privateStaticField_fid));
    const char *privateStaticField_content = env->GetStringUTFChars(privateStaticField_obj,
                                                                    nullptr);

普通属性

与访问静态属性差不多,访问普通属性在JNI访问java类属性分为两个步骤

  1. 首先是通过FindClass函数找到对应的类

  2. 然后通过GetFieldID找到对应的属性,如果需要修改变量的话则通过一系列的SetTypeField函数进行修改即可.

  // 获取对应的类
    jclass jclazz = env->FindClass("com/fly/jnitest/Person");
    // 获取属性,第三个参数是属性描述符
    jfieldID nameFielid = env->GetFieldID(jclazz,"name","Ljava/lang/String;");
    // 修改成员属性
    env->SetObjectField(thiz,nameFielid,env->NewStringUTF("james"));

数组操作

创建数组

使用New<Type>Array函数创建一个数组实例,其中Type为基本数据类型:Boolean、Byte、Char、Short、Int、Long、Float、Double,如NewIntArray

jintArray array = env->NewIntArray(4);
if (0 != array) {
    // 内存溢出的情况下,NewIntArray返回NULL
}

访问和更新数组

extern "C" JNIEXPORT void JNICALL
Java_smarttime_tsia_com_jnitest3_MainActivity_updateArray(
        JNIEnv* env, jobject obj, jfloatArray jnums) {
    // 获取数组长度
    jsize len = env->GetArrayLength(jnums);

    // JNI数组复制到c数组
    jfloat buffer[2];
    env->GetFloatArrayRegion(jnums, 0, len, buffer);

    // 更新c数组
    buffer[0] = 2.2f;

    // 将修改提交到JNI数组中
    env->SetFloatArrayRegion(jnums, 0, len, buffer);
}
extern "C" JNIEXPORT void JNICALL
Java_smarttime_tsia_com_jnitest3_MainActivity_updateArray(
        JNIEnv* env, jobject obj, jfloatArray jnums) {
    jboolean isCopy;
    // 获取直接指针
    jfloat *parray = env->GetFloatArrayElements(jnums, &copy);
    if (0==parray) {
        return;
    }

    // 更新c数组
    parray[1] = 2.2f;

    env->ReleaseFloatArrayElements(jnums, parray, 0);
}

Get<Type>ArrayElements函数获取指向数组元素的直接指针,这个函数的最后一个参数是isCopy,让调用者确定返回的c指针是指向数组副本,还是指向原始数组。假如我们需要对一个jni数组临时改变一下,然后给其他方法用,但并不希望提交到java数组。这时我们可以通过isCopy判断如果是false说明是原始数据,那我们就需要拷贝一个副本用来修改,否则会修改java数组;假如是true,就不需要另外拷贝一个副本。

Release<Type>ArrayElements的最后一个参数mode为释放模式,其值和意义为:

  • 0:c数组修改后,将其复制到jni数组,并释放c数组。

  • JNI_COMMIT:c数组修改后,将其复制到jni数组,但不释放c数组。这种一般用于周期性的更新一个java数组。

  • JNI_ABORT:c数组修改后,不将其复制到jni数组,并释放c数组。也就是上述例子设置JNI_ABORT的话,java的数组并不会被改变。

访问方法

静态方法

静态方法只需要类,不需要具体对象

jclass jclazz = env->FindClass("com/example/lammyopenglffmpegvideoplayer/LammyOpenglVideoPlayerView");
jmethodID testMethod_id= env->GetStaticMethodID(jclazz,"testMethod","()V");
// 执行
env->CallStaticVoidMethod(jclazz, testMethod_id);

非静态方法

// 获取对应的类
jclass jclazz = env->FindClass("com/fly/jnitest/Person");
// 获取方法id,第三个参数是函数签名
jmethodID methodId = env->GetMethodID(jclazz,"printName", "()V");
// 调用java方法
env->CallVoidMethod(thiz, methodId);

访问方法有三大类:

  • CallMethod

Programmers place all arguments that are to be passed to the method immediately following the methodID argument. The CallMethod routine accepts these arguments and passes them to the Java method that the programmer wishes to invoke.

  • CallMethodA

Programmers place all arguments to the method in an args array of jvalues that immediately follows the methodID argument. The CallMethodA routine accepts the arguments in this array, and, in turn, passes them to the Java method that the programmer wishes to invoke.

  • CallMethodV

Programmers place all arguments to the method in an args argument of type va_list that immediately follows the methodID argument. The CallMethodV routine accepts the arguments, and, in turn, passes them to the Java method that the programmer wishes to invoke.

这3种方式其实是一样的,只不过接受的参数类型不一样,按需选择最简单的即可。

调用父类方法

使用 jni 实现 activity 的 onCreate 方法:

extern "C" JNIEXPORT void JNICALL
Java_com_kanxue_reflectiontest_MainActivity_onCreate(
        JNIEnv *env,
        jobject thiz, jobject bundle) {
  // super 类
    jclass AppCompatActivity_jclass1 = env->FindClass("androidx/appcompat/app/AppCompatActivity");

    jclass MainActivity_jclass1 = env->FindClass("com/kanxue/reflectiontest/MainActivity");

    jclass MainActivity_jclass2 = env->GetObjectClass(thiz);
  // super 类
    jclass AppCompatActivity_jclass2 = env->GetSuperclass(MainActivity_jclass2);

    jmethodID superClassOnCreate_mid = env->GetMethodID(AppCompatActivity_jclass2, "onCreate",
                                                        "(Landroid/os/Bundle;)V");
  // 调用 super 类的方法
    env->CallNonvirtualVoidMethod(thiz, AppCompatActivity_jclass2, superClassOnCreate_mid, bundle);

}

上面展现了两种方式来获取 super 类。

NativeType CallNonvirtual<type>Method(JNIEnv *env, jobject obj, jclass clazz, jmethodID methodID, ...);

CallNonvirtual<Type>Method 的 methodID 参数必须是从 clazz 里面获取的。

管理局部引用

Java 提供了一些函数来管理局部引用的生命周期:

  • EnsureLocalCapacity

  • NewLocalRef

  • PushLocalFrame

  • PopLocalFrame

EnsureLocalCapacity 函数

JNI 的规范指出,JVM 要确保每个 Native 方法至少可以创建 16 个局部引用,经验表明,16 个局部引用已经足够平常的使用了。

但是,如果要与 JVM 的中对象进行复杂的交互计算,就需要创建更多的局部引用了,这时就需要使用 EnsureLocalCapacity 来确保可以创建指定数量的局部引用,如果创建成功返回 0 ,返回返回小于 0 ,如下代码示例:

    // Use EnsureLocalCapacity
    int len = 20;
    if (env->EnsureLocalCapacity(len) < 0) {
        // 创建失败,out of memory
    }
    for (int i = 0; i < len; ++i) {
        jstring  jstr = env->GetObjectArrayElement(arr,i);
        // 处理 字符串
        // 创建了足够多的局部引用,这里就不用删除了,显然占用更多的内存
    }

引用确保可以创建了足够的局部引用数量,所以在循环处理局部引用时可以不进行删除了,但是显然会消耗更多的内存空间了。

PushLocalFrame 与 PopLocalFrame 函数对

PushLocalFrame 与 PopLocalFrame 是两个配套使用的函数对。

它们可以为局部引用创建一个指定数量内嵌的空间,在这个函数对之间的局部引用都会在这个空间内,直到释放后,所有的局部引用都会被释放掉,不用再担心每一个局部引用的释放问题了。

常见的使用场景就是在循环中:

    // Use PushLocalFrame & PopLocalFrame
    for (int i = 0; i < len; ++i) {
        if (env->PushLocalFrame(len)) { // 创建指定数据的局部引用空间
            //out ot memory
        }
        jstring jstr = env->GetObjectArrayElement(arr, i);
        // 处理字符串
        // 期间创建的局部引用,都会在 PushLocalFrame 创建的局部引用空间中
        // 调用 PopLocalFrame 直接释放这个空间内的所有局部引用
        env->PopLocalFrame(NULL); 
    }

使用 PushLocalFrame & PopLocalFrame 函数对,就可以在期间放心地处理局部引用,最后统一释放掉。PopLocalFrame 还可以接受一个局部引用作为参数,将这个引用返回给调用的函数。

  • 21
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值