写点什么

Android C++ 系列:JNI 调用时缓存字段和方法 ID

作者:轻口味
  • 2021 年 11 月 11 日
  • 本文字数:1707 字

    阅读完需:约 6 分钟

Android C++系列:JNI 调用时缓存字段和方法 ID

在 JNI 去调用 Java 的方法和访问字段时,最先要做的操作就是获得对应的类以及对应的方法 id。


通常我们通过 FindClass 、GetFieldID、GetMethodID 去找到对应的信息也是耗时操作,如果方法被频繁调用(特别是像音视频处理时循环的调用 JNI 方法传递音视频数据),每次都去查找对应的类和方法 ID 会很耗性能,所以我们必须将它们缓存起来,达到只创建一次,后面直接使用缓存内容的效果。


缓存有两种方式,分别是使用时缓存和初始化时缓存。

使用时缓存

使用时缓存,就是在调用时查找一次,然后将它缓存成 static 静态变量,这样下次调用时直接使用即可。直到内存释放了,才会缓存失效。


extern "C" JNIEXPORT void JNICALL Java_com_qingkouwei_demo_CacheFieldAndMethodDemo_staticCacheField(JNIEnv *env, jobject instance, jobject fruit) {     static jfieldID fid = NULL; // 声明为 static 变量进行缓存     // 两种方法都行 //    jclass cls = env->GetObjectClass(animal);     jclass cls = env->FindClass("com/qingkouwei/demo/bean/Fruit");     jstring jstr;     const char *c_str;     // 从缓存中查找     if (fid == NULL) {         fid = env->GetFieldID(cls, "name", "Ljava/lang/String;");         if (fid == NULL) {             return;         }     } else {         LOGD("field id is cached");     }     jstr = (jstring) env->GetObjectField(fruit, fid);     c_str = env->GetStringUTFChars(jstr, NULL);     if (c_str == NULL) {         return;     }     env->ReleaseStringUTFChars(jstr, c_str);     jstr = env->NewStringUTF("new name");     if (jstr == NULL) {         return;     }     env->SetObjectField(animal, fid, jstr); }
复制代码


通过声明为 static 变量进行缓存的方式显然有弊端,当多个调用者同时调用时,就会出现缓存多次的情况,并且每次调用时都要检查是否缓存过了。这种情况我们可以用下一种方式,做一个初始化方法,初始化时缓存一次。

初始化时缓存

在初始化时缓存,就是在类加载时,进行缓存。当类被加载进内存时,会先调用类的静态代码块,所以可以在类的静态代码块中进行缓存。


比如:


public class CacheFieldAndMethodDemo {          static {         init(); // 静态代码块中进行缓存     }     private static native void init(); }
复制代码


在静态代码块中,可以将所需要的字段 id 或者方法 id 缓存成全局变量。


具体代码如下:


// 全局变量,作为缓存方法 id jmethodID InstanceMethodCache; // 初始化加载时缓存方法 id extern "C" JNIEXPORT void JNICALL Java_com_qingkouwei_demo_CacheFieldAndMethodDemo_init(JNIEnv *env, jclass type) {     jclass cls = env->FindClass("com/qingkouwei/demo/bean/Fruit");     InstanceMethodCache = env->GetMethodID(cls, "getName", "()Ljava/lang/String;"); }
复制代码


在 JNI 中直接将方法 id 缓存成全局变量了,这样再调用时,就不要再进行一次查找了,并且避免了多个线程同时调用会多次查找的情况。


extern "C" JNIEXPORT void JNICALL Java_com_qingkouwei_demo_CacheFieldAndMethodDemo_callCacheMethod(JNIEnv *env, jobject instance, jobject animal) {     jstring name = (jstring) env->CallObjectMethod(animal, InstanceMethodCache);     const char *c_name = env->GetStringUTFChars(name, NULL);     LOGD("call cache method and value is %s", c_name); }
复制代码

总结

之前分享的 JNI 操作是基础,基于应用到实战中就要讲究各种技巧,用以提升效率。本文提到 JNI 调用时字段和方法 ID 缓存其实最终的方案还是以初始化时缓存为主,是在实际开发中总结出来的。


之前在 Android 平台封装了 lamemp3、opus 等音频编码器,都是使用线程的 C 语言实现的编码器,然后在 Java 层利用 AudioRecorder 录音,然后将音频数据通过 JNI 调用到这些编码器编码方法,这些接口的主要流程是:


  1. 创建编码器;

  2. 循环调用编码器编码方法,输入 pcm 数据,输出编码后音频数据;

  3. 释放编码器。


这里面就可以把创建编码器充当初始化方法来获取编码方法的方法 id 等。

发布于: 19 小时前阅读数: 8
用户头像

轻口味

关注

🏆2021年InfoQ写作平台-签约作者 🏆 2017.10.17 加入

Android、音视频、AI相关领域从业者。 邮箱:qingkouwei@gmail.com

评论

发布
暂无评论
Android C++系列:JNI 调用时缓存字段和方法 ID