获取字段或方法ID时,需要用字段、方法的名字和描述进行一个检索。检索过程比较耗时,可以通过缓存技术来减少这个过程带来的消耗。
缓存字段ID和方法ID的方法主要有两种。
1.使用时缓存
2.定义字段和方法的类静态初始化时缓存
使用时缓存
字段或方法ID在字段的值被访问或者方法被回调的时候缓存起来。
1.先定义两个native方法
//缓存字段(使用时)
public native void cacheFieldWhenUse();
//缓存方法ID(使用时)
public native String cacheMethodWhenUse(char[] chars);
2.生成c头文件
JNIEXPORT void JNICALL Java_com_test_git_jnidemo_JniUtil_JniDemo_cacheFieldWhenUse
(JNIEnv *, jobject);
JNIEXPORT jstring JNICALL Java_com_test_git_jnidemo_JniUtil_JniDemo_cacheMethodWhenUse
(JNIEnv *, jobject, jcharArray);
3.方法实现
3.1缓存字段
下面方法主要实现的是:获取name字段值并修改。
JNIEXPORT void JNICALL Java_com_test_git_jnidemo_JniUtil_JniDemo_cacheFieldWhenUse
(JNIEnv *env, jobject jobj){
//0.静态变量,用于缓存fid
static jfieldID fid = NULL;
//1.获取jclass
jclass cls = (*env).GetObjectClass(jobj);
//2.获取name的fid
if(fid == NULL){
fid = (*env).GetFieldID(cls, "name", "Ljava/lang/String;");
//获取失败
if(fid == NULL){
return; //error
}
}
//3.获取name字段值
jstring jstr = (jstring) (*env).GetObjectField(jobj, fid);
//4.将字符串转换成字符指针
const char *str = (*env).GetStringUTFChars(jstr, NULL);
//4.1如果为NULL,则发生了内存溢出
if(str == NULL){
return; //out of memory
}
//5.将str字符添加到str_new后面
char *c = "缓存字段测试 ";
char new_char[strlen(str) + strlen(c)];
//复制c_str 到 new_char
strcpy(new_char, c);
strcat(new_char, str);
//6.及时释放str
(*env).ReleaseStringUTFChars(jstr, str);
//7.重新生成jstr
jstr = (*env).NewStringUTF(new_char);
//7.1如果为NULL,则发生了内存溢出
if(jstr == NULL){
return; //out of memory
}
//8.修改name字段值
(*env).SetObjectField(jobj, fid, jstr);
};
3.2缓存方法ID
下面的方法实现的是将java中传递过来的字符数组转换成字符串返回
JNIEXPORT jstring JNICALL Java_com_test_git_jnidemo_JniUtil_JniDemo_cacheMethodWhenUse
(JNIEnv *env, jobject jobj, jcharArray elemArr){
//1.要缓存的方法ID:cid
static jmethodID cid = NULL;
//2.获取String的jclass
jclass stringClass = (*env).FindClass("java/lang/String");
if(stringClass == NULL){
return NULL; //exception
}
//3.判断cid是否已经初始化
if(cid == NULL){
cid = (*env).GetMethodID(stringClass, "<init>", "([C)V");
if(cid == NULL){
return NULL; //exception
}
}
//4.把传入的字符数组转换成字符串
jstring result = (jstring) (*env).NewObject(stringClass, cid, elemArr);
//5.本地引用引用
/* free local references */
(*env).DeleteLocalRef(elemArr);
(*env).DeleteLocalRef(stringClass);
//6.返回字符串
return result;
};
java中调用
Log.i(TAG, "------------------------------------------");
Log.i(TAG, "name修改前: " + jd.name);
jd.cacheFieldWhenUse();
Log.i(TAG, "name修改后: " + jd.name);
Log.i(TAG, "------------------------------------------");
char[] chars = {'a', 'b', 'c'};
Log.i(TAG, "result传入:{'a', 'b', 'c'}");
String result = jd.cacheMethodWhenUse(chars);
Log.i(TAG, "result输出:" + result);
Log.i(TAG, "------------------------------------------");
输出结果
09-29 12:41:42.431 25020-25020/com.test.git.jnidemo I/MainActivity-: ------------------------------------------
09-29 12:41:42.431 25020-25020/com.test.git.jnidemo I/MainActivity-: name修改前: changed Lucy
09-29 12:41:42.431 25020-25020/com.test.git.jnidemo I/MainActivity-: name修改后: 缓存字段测试 changed Lucy
09-29 12:41:42.431 25020-25020/com.test.git.jnidemo I/MainActivity-: ------------------------------------------
09-29 12:41:42.431 25020-25020/com.test.git.jnidemo I/MainActivity-: result传入:{'a', 'b', 'c'}
09-29 12:41:42.431 25020-25020/com.test.git.jnidemo I/MainActivity-: result输出:abc
09-29 12:41:42.431 25020-25020/com.test.git.jnidemo I/MainActivity-: ------------------------------------------
tips
上面的方法多次调用的时候缓存的字段或方法只会初始化一次,因而提高了效率。
类的静态初始化过程中缓存字段和方法 ID
1.先写两个native方法
//缓存(初始化时)
public static native void cacheWhenInit();
//使用上面方法的缓存
public native String cacheWhenInitInvoke(char[] chars);
2.生成相应的头文件
JNIEXPORT void JNICALL Java_com_test_git_jnidemo_JniUtil_JniDemo_cacheWhenInit
(JNIEnv *, jclass);
JNIEXPORT jstring JNICALL Java_com_test_git_jnidemo_JniUtil_JniDemo_cacheWhenInitInvoke
(JNI
3.方法实现
3.1缓存jmethodID
//缓存的String的String(char value[])方法id
jmethodID strInitID;
JNIEXPORT void JNICALL Java_com_test_git_jnidemo_JniUtil_JniDemo_cacheWhenInit
(JNIEnv *env, jclass jcls){
//1.获取String的jclass
jclass strClass = (*env).FindClass("java/lang/String");
if(strClass == NULL){
return; //exception
}
//2.判断cid是否已经初始化
if(strInitID == NULL){
strInitID = (*env).GetMethodID(strClass, "<init>", "([C)V");
if(strInitID == NULL){
return; //exception
}
}
};
3.2直接使用3.1缓存的ID
//使用上面的strInitID
JNIEXPORT jstring JNICALL Java_com_test_git_jnidemo_JniUtil_JniDemo_cacheWhenInitInvoke
(JNIEnv *env, jobject jobj, jcharArray chars){
jclass stringClass = (*env).FindClass("java/lang/String");
//把传入的字符数组转换成字符串
return (jstring) (*env).NewObject(stringClass, strInitID, chars);
};
4.JAVA调用过程
4.1首先初始化时调用
static {
System.loadLibrary("NdkJniDemo");
cacheWhenInit();
}
4.2在其他地方直接使用
Log.i(TAG, "------------------------------------------");
Log.i(TAG, "result传入:{'a', 'b', 'c'}");
result = jd.cacheWhenInitInvoke(chars);
Log.i(TAG, "result输出:" + result);
Log.i(TAG, "------------------------------------------");
4.3输出结果:
09-29 12:41:42.431 25020-25020/com.test.git.jnidemo I/MainActivity-: ------------------------------------------
09-29 12:41:42.431 25020-25020/com.test.git.jnidemo I/MainActivity-: result传入:{'a', 'b', 'c'}
09-29 12:41:42.431 25020-25020/com.test.git.jnidemo I/MainActivity-: result输出:abc
09-29 12:41:42.431 25020-25020/com.test.git.jnidemo I/MainActivity-: ------------------------------------------
两种缓存方式对比
如果JNI程序员不能控制方法和字段所在的类的源码的话,在使用时缓存是个合理的方案。
比起静态初始时缓存来说,使用时缓存有一些缺点:
1.使用时缓存的话,每次使用时都要检查一下。
2.方法ID和字段ID在类被unload时就会失效,如果你在使用时缓存ID,你必须确保只要本地依赖于这个ID的值,那么这个类不会被unload。另一方面,如果缓存发生在静态初始化时,当类被unload和reload时,ID会被重新计算。
因此,尽可能在静态初始化时缓存字段ID和方法ID