Android序列化中由于修改类的内容造成InvalidClassException的解决方案

好多年没写博文了,我果然没救了。不过这篇文章定义应该是符合并不是互联网里满大街的知识点的要求吧。

问题回顾

在我的Exlink2.0版本升级到2.1中,之前保存的规则信息全部被清空了,而我的规则信息是通过对象序列化存储的。经初步排查,报错信息为

java.io.InvalidClassException: com.xloger.exlink.app.b.c; Incompatible class (SUID): com.xloger.exlink.app.b.c: static final long serialVersionUID =86222585354990243L; but expected com.xloger.exlink.app.b.c: static final long serialVersionUID =-4642583507468338732L;

解决这个报错的方案网上有很多,但是尝试后并没有解决我的问题,因此写下这篇博文。

序列化与serialVersionUID

序列化就是将一个对象转换为一个字节序列(目的是能存储和传输对象)。而上面的InvalidClassException恰好就是在反序列化(将一个字节序列转换回一个对象)时会产生的。

而这个InvalidClassException的原因是这样的:在一个对象(比如继承自Rule类)与字节序列的相互转换中,必然需要一个标记来证明它是Rule类的对象,否则谁知道你是Rule类还是String类还是什么,那没法反序列化了。因此,Java规定了一个属性serialVersionUID来区分它们,这个属性是可以在你需要序列化的类里申明的,比如这样:

private static final long serialVersionUID=10086L;

设定为private是为了不被子类继承。当然了,大部分人会说我以前没写过serialVersionUID啊,都是继承了Serializable接口就没了啊,是的,当没有申明serialVersionUID的时候,JVM会自动地根据包名、类名、继承关系、非私有的方法和属性,以及参数、返回值等诸多因子计算出一个值。比如我的Exlink在2.0版本之前,它给我计算的这个值为86222585354990243L,而在2.1版本中我对Rule类做了一些修改,导致该serialVersionUID变成了-4642583507468338732L。因此虽然我做的改动并不会真的影响到反序列化,但是JVM依旧会认为这有问题,因此抛出了InvalidClassException。

Ok,解决方案很简单,为了兼容以前的版本,我仅仅需要在Rule类里把serialVersionUID定义为86222585354990243L,这样JVM在反序列化的时候依旧会把最新版的Rule和旧的Rule当成一个来处理了。

也许有人会纳闷一些兼容性的问题,首先分成向上兼容、向下兼容两种。假如我们的新版增加了一个属性比如age,向上兼容就是旧的客户端读取新的数据,这里JVM会自动把那些它不认识的数据忽略掉(比如age),所以只要不改动旧属性,不用担心向上兼容的问题。向下兼容是新的客户端读取旧的数据,对于新的属性都会为Java里该类型的默认值,比如null、0、false这些。哪怕你定义你的成员变量private int age=12,这里的值依旧是0。那想做向下兼容应该怎么处理呢,就是在读取数据之后进行检测,判断是旧版本后手动初始化,比如可以维护一个当前类的版本的变量来判断。或者Android可以直接读取程序的versionCode来作为判断变量。

混淆与serialVersionUID

当然,我改了serialVersionUID后依旧没成功,否则我就没必要写这篇博文了……我发现在我改了serialVersionUID后,报的异常依旧还是那两个long值,我的serialVersionUID没有生效。然后想了是不是并不是该类的问题,或者serialVersionUID是依赖hashCode方法的由于我重写了它导致没有生效,在我想的天昏地暗后三三提醒我看看是不是混淆器干掉了serialVersionUID,反编译后看了下果然是……(Orz其实我的博文核心只有这一句话么……)

解决方案依旧很简单,在混淆配置文件(proguard-rules.pro)里加上:

-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

问题解决。这部分代码是从http://www.jianshu.com/p/f3455ecaa56e 拷的,想了解更多混淆注意知识点的可以去参考下。

总结

一,当时真不应该图省事用对象序列化存储,用数据库就没这么多破事了;

二,对序列化想了解的更深入些的可以看看这个:http://hxraid.iteye.com/blog/461935

三,在搜“混淆 serialVersionUID”关键字寻找是否有人已经描述了这个小坑的时候,找到了一个可以用注解避免混淆的小技巧:http://www.trinea.cn/android/android-proguard-tip-not-proguard/

四,Rule这种要被序列化的对象是不应该被混淆的,是我的锅。不过不推荐随便复制别人代码(一定要确定你能看懂对方的每行代码后再拷),比如不能我这次被坑后结果下次就网上拷一个混淆规则进去。我的编程真理是“瞎JB复制代码,总有一天是要还的”

五,我真的该换个WordPress主题了,这引用格式的字号……

六,还是三三靠谱,人肉搜索引擎。

发布者:xloger

在无限延伸的梦想后面 穿越冷酷无情的世界

留下评论

电子邮件地址不会被公开。 必填项已用*标注