在 JNI 去调用 Java 的方法和访问字段时,最先要做的操作就是获得对应的类以及对应的方法 id。
事实上,通过 FindClass 、GetFieldID、GetMethodID 去找到对应的信息是很耗时的,如果方法被频繁调用,那么肯定不能每次都去查找对应的信息,有必要将它们缓存起来,在下一次调用时,直接使用缓存内容就好了。
缓存有两种方式,分别是使用时缓存和初始化时缓存。

使用时缓存

使用时缓存,就是在调用时查找一次,然后将它缓存成 static 变量,这样下次调用时就已经被初始化过了。
直到内存释放了,才会缓存失效。
1extern"C"
2
JNIEXPORT 
void
 JNICALL

3Java_com_glumes_cppso_jnioperations_CacheFieldAndMethodOps_staticCacheField(JNIEnv *env, jobject instance, jobject animal)
{

4static
 jfieldID fid = 
NULL
// 声明为 static 变量进行缓存
5// 两种方法都行
6//    jclass cls = env->GetObjectClass(animal);
7
    jclass cls = env->FindClass(
"com/glumes/cppso/model/Animal"
);

8
    jstring jstr;

9constchar
 *c_str;

10// 从缓存中查找
11if
 (fid == 
NULL
) {

12
        fid = env->GetFieldID(cls, 
"name"
"Ljava/lang/String;"
);

13if
 (fid == 
NULL
) {

14return
;

15
        }

16
    } 
else
 {

17
        LOGD(
"field id is cached"
);

18
    }

19
    jstr = (jstring) env->GetObjectField(animal, fid);

20
    c_str = env->GetStringUTFChars(jstr, 
NULL
);

21if
 (c_str == 
NULL
) {

22return
;

23
    }

24
    env->ReleaseStringUTFChars(jstr, c_str);

25
    jstr = env->NewStringUTF(
"new name"
);

26if
 (jstr == 
NULL
) {

27return
;

28
    }

29
    env->SetObjectField(animal, fid, jstr);

30
}

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

初始化时缓存

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

2
3static
 {

4
        initCacheMethodId(); 
// 静态代码块中进行缓存
5
    }

6privatestaticnativevoidinitCacheMethodId()
;

7
}

在静态代码块中,可以将所需要的字段 id 或者方法 id 缓存成全局变量。
具体代码如下:
1// 全局变量,作为缓存方法 id
2
jmethodID InstanceMethodCache;

3
4// 初始化加载时缓存方法 id
5extern"C"
6
JNIEXPORT 
void
 JNICALL

7Java_com_glumes_cppso_jnioperations_CacheFieldAndMethodOps_initCacheMethodId(JNIEnv *env, jclass type)
{

8
    jclass cls = env->FindClass(
"com/glumes/cppso/model/Animal"
);

9
    InstanceMethodCache = env->GetMethodID(cls, 
"getName"
"()Ljava/lang/String;"
);

10
}

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

3Java_com_glumes_cppso_jnioperations_CacheFieldAndMethodOps_callCacheMethod(JNIEnv *env, jobject instance, jobject animal)
{

4
    jstring name = (jstring) env->CallObjectMethod(animal, InstanceMethodCache);

5constchar
 *c_name = env->GetStringUTFChars(name, 
NULL
);

6
    LOGD(
"call cache method and value is %s"
, c_name);

7
}

小结

可以看出,如果不能预先知道方法和字段所在类的源码,那么在使用时缓存比较合理。但如果知道的话,在初始化时缓存优点较多,既避免了每次使用时检查,还避免了在多线程被调用的情况。
具体示例代码可参考我的 Github 项目,欢迎 Star。
https://github.com/glumes/AndroidDevWithCpp
欢迎关注微信公众号:【纸上浅谈】,获得最新文章推送~~
扫码关注
继续阅读
阅读原文