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

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

image.png


在 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等。

目录
相关文章
|
21天前
|
缓存 Java Shell
Android 系统缓存扫描与清理方法分析
Android 系统缓存从原理探索到实现。
45 15
Android 系统缓存扫描与清理方法分析
|
19天前
|
存储 缓存 监控
利用 Redis 缓存特性避免缓存穿透的策略与方法
【10月更文挑战第23天】通过以上对利用 Redis 缓存特性避免缓存穿透的详细阐述,我们对这一策略有了更深入的理解。在实际应用中,我们需要根据具体情况灵活运用这些方法,并结合其他技术手段,共同保障系统的稳定和高效运行。同时,要不断关注 Redis 缓存特性的发展和变化,及时调整策略,以应对不断出现的新挑战。
52 10
|
19天前
|
缓存 监控 NoSQL
Redis 缓存穿透的检测方法与分析
【10月更文挑战第23天】通过以上对 Redis 缓存穿透检测方法的深入探讨,我们对如何及时发现和处理这一问题有了更全面的认识。在实际应用中,我们需要综合运用多种检测手段,并结合业务场景和实际情况进行分析,以确保能够准确、及时地检测到缓存穿透现象,并采取有效的措施加以解决。同时,要不断优化和改进检测方法,提高检测的准确性和效率,为系统的稳定运行提供有力保障。
47 5
|
1月前
|
缓存 NoSQL 算法
解决Redis缓存雪崩问题的有效方法
解决Redis缓存雪崩问题的有效方法
42 1
|
2月前
|
存储 缓存 NoSQL
解决Redis缓存击穿问题的技术方法
解决Redis缓存击穿问题的技术方法
68 2
|
2月前
|
缓存 NoSQL Redis
解决 Redis 缓存穿透问题的有效方法
解决 Redis 缓存穿透问题的有效方法
52 2
|
2月前
|
存储 缓存 Android开发
Android RecyclerView 缓存机制深度解析与面试题
本文首发于公众号“AntDream”,详细解析了 `RecyclerView` 的缓存机制,包括多级缓存的原理与流程,并提供了常见面试题及答案。通过本文,你将深入了解 `RecyclerView` 的高性能秘诀,提升列表和网格的开发技能。
66 8
|
2月前
|
ARouter 测试技术 API
Android经典面试题之组件化原理、优缺点、实现方法?
本文介绍了组件化在Android开发中的应用,详细阐述了其原理、优缺点及实现方式,包括模块化、接口编程、依赖注入、路由机制等内容,并提供了具体代码示例。
45 2
|
3月前
|
开发工具 uml git
AOSP源码下载方法,解决repo sync错误:android-13.0.0_r82
本文分享了下载AOSP源码的方法,包括如何使用repo工具和处理常见的repo sync错误,以及配置Python环境以确保顺利同步特定版本的AOSP代码。
423 0
AOSP源码下载方法,解决repo sync错误:android-13.0.0_r82
|
6天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
29 4