现场回顾:
1,在JNI开发过程中,遇到需要使用Integer进行参数传入传出的场景,比如结果为3。
2,在步骤1的jni层通过Integer传出的结果在一个八竿子打不着的地方使用Gson进行json字符串转换成实体对象的时候,所有int整形的变量都被赋值为3,但原始的值为0。
java接口
//不适用返回值传递的原因是,返回值被设计用来指示接口执行成功==0,其他为错误码。当成功时,再通过integer传递结果。
public native int func( Integer integer);
jni实现
extern "C"
JNIEXPORT jint JNICALL
Java_com_xxx_func(JNIEnv *env,jobject thiz,jobject jobjInteger) {
uint result = 0;
//省略其他无关代码
...
//
result = 3;
jclass jcsInteger = env->FindClass("java/lang/Integer");
jfieldID jfdValue = env->GetFieldID(jcsInteger, "value", "I");
env->SetIntField(jobjInteger, jfdValue, result);
return ret;
}
另外一个八竿子打不着的地方
public class Bean{
private int type;
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
}
调用(bug产生在这里)
public void demo(){
//未通过new进行实例化,而是直接赋值,这里是代码段1。使用integerA = new Integer(0)后解决。
Integer integerA = 0;
func(integerA);//这里达到了目的,integerA成功被修改为3,并传递了出来。
//另外一个八竿子打不着的地方,这里是代码段2
String jsonString = "{"type": 0}";
Bean bean = new Gson().fromJson(jsonString,Bean.class);//内部肯定是发送了Integer自动装箱,源代码没找见,难道我分析的不对!!!!???
//bean的type变量值,在这里不是期望的1,而是3!!!!
//当然在项目里面的代码要比这里赋值的多,这篇文章是为了总结方便,把相关代码抽出来做成了案例方便回顾。
}
原因分析:
刚开始并未注意Integer,怀疑完Gson,怀疑多线程并发,怀疑完并发,怀疑jni,最后在怀疑一整晚人生后,痛定思痛,在使用排除法最后定位规律后,得出上面demo方法里面的规律,jni返回多少,json的整形就会修改多少。
解决方案:
排查代码后,一眼看出,integerA未进行实例化方式创建,使用Integer integerA = new Integer(0)后解决。
重拾基础:
问题虽然解决了,但是本着寻根溯源的宗旨,需要去彻底理解问题产生的原因,既然是用Integer出了问题,那就是自己使用Intger方法不当,还是基础不牢靠,才会被打回原形。
关键原因
-Integer提前将-128~127之间的Integer对象地址缓存在常量池中, Integer integer = 0 这样定义,会发生自动装箱,即Integer.valueOf(0),使用常量池缓存,不会创建新的对象,我们的这个BUG中,通过反射修改了常量值为3,造成开始常量值声明为0的,所有integer的值被修改。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
- 两段代码恰好都定义了Integer integer = 0; 在通过反射修改内部声明的value为3后,两个引用指向的值都被修改成了3。