前言
在 JNI
中如果需要调用 Java
的方法,首先需要使用 FindClass
获取到 jclass
,然后使用 GetMethodID
获取到对应方法的 ID
,字段也是一样的要使用 GetFieldID
来获取。
如果每次都要查找一遍,是比较麻烦和耗时的,今天就来研究一下怎么把它们缓存起来以提高性能。
jclass
先来看看 jclass
是什么
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
#ifdef __cplusplus
/*
* Reference types, in C++
*/
class _jobject {};
class _jclass : public _jobject {};
typedef _jclass* jclass;
// ...
#else /* not __cplusplus */
/*
* Reference types, in C.
*/
typedef void* jobject;
typedef jobject jclass;
//...
#endif /* not __cplusplus */
|
从源码中可以发现 jclass
是个引用类型,它所对应的 Java
类是 java.lang.Class
。
我们通过 FindClass
获取到的 jclass
是个局部引用,所以如果要缓存起来需要使用全局引用,因为局部引用会在栈退出后在触发 GC
的情况下被释放。
写个例子来看看获取到的局部引用是不是每次都不一样
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
class MainActivity : AppCompatActivity() {
companion object {
init {
System.loadLibrary("jcache")
}
}
external fun printClassAddr()
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
printClassAddr()
printClassAddr()
printClassAddr()
}
}
|
对应 C++
代码
1
2
3
4
5
6
7
8
9
|
#include <jni.h>
#include <android/log.h>
extern "C"
JNIEXPORT void JNICALL
Java_com_example_MainActivity_printClassAddr(JNIEnv *env, jobject thiz) {
jclass clazz = env->FindClass("com/example/MainActivity");
__android_log_print(ANDROID_LOG_VERBOSE, "JNI", "class address: %p", clazz);
}
|
结果如下
1
2
3
|
V/JNI: class address: 0x10001d
V/JNI: class address: 0x20001d
V/JNI: class address: 0x1d
|
从输出结果上来看,每次获取到的 jclass
地址都不一样,也就是我们说的它是个局部引用,需要用 NewGloabalRef
给缓存起来。
jmethodID/jfieldID
说完了 jclass
接下里看看 jmethodID
和 jfieldID
,依旧先来看下源码
1
2
3
4
5
|
struct _jfieldID; /* opaque structure */
typedef struct _jfieldID* jfieldID; /* field IDs */
struct _jmethodID; /* opaque structure */
typedef struct _jmethodID* jmethodID; /* method IDs */
|
从代码中看到 jmethodID
和 jfieldID
就是个指针,注释中说是不透明的类型,理解为指针就可以了。
由于 jmethodID
和 jfieldID
是指针,不是局部引用,所以可以直接保存起来,而不应该使用 NewGlobalRef
保存。
接下来依旧写个例子打印一下它们的地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
class MainActivity : AppCompatActivity() {
companion object {
init {
System.loadLibrary("jcache")
}
}
external fun printClassAddr()
private lateinit var binding: ActivityMainBinding
var name:String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
printClassAddr()
printClassAddr()
printClassAddr()
}
}
|
对应 C++
代码
1
2
3
4
5
6
7
8
9
10
11
|
#include <jni.h>
#include <android/log.h>
extern "C"
JNIEXPORT void JNICALL
Java_com_example_MainActivity_printClassAddr(JNIEnv *env, jobject thiz) {
jclass clazz = env->FindClass("com/example/MainActivity");
jmethodID construct = env->GetMethodID(clazz, "onCreate", "(Landroid/os/Bundle;)V");
jfieldID nameId = env->GetFieldID(clazz, "name", "Ljava/lang/String;");
__android_log_print(ANDROID_LOG_VERBOSE, "JNI", "methodId address: %p, fieldId address: %p", construct, nameId);
}
|
结果如下
1
2
3
|
V/JNI: methodId address: 0x7f8fada1a8, fieldId address: 0x7f8fada02c
V/JNI: methodId address: 0x7f8fada1a8, fieldId address: 0x7f8fada02c
V/JNI: methodId address: 0x7f8fada1a8, fieldId address: 0x7f8fada02c
|
可以看到,连续调用多次的情况下它们的值是相同的,这也印证了,我们可以直接保存的结论。
总结
jclass
是个局部引用,每次获取到的值不一定相同,需要使用 NewGlobalRef
来缓存
jmethodId
和 jfieldID
是个指针,每次获取到的值是一样的,可以直接保存起来
参考