首页 > 解决方案 > 即使设置了 serialVersionUID,反序列化也会引发 InvalidClassException

问题描述

前段时间我发布了一个序列化/反序列化用户对象的应用程序。

public String serializeUser(final User user) {
    final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    try {
        final ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(user);
        objectOutputStream.close();
    } catch (final IOException exception) {
        ...
    }

    return new String(Base64.encode(byteArrayOutputStream.toByteArray(), DEFAULT));
}

public User deserializeString(final String userString) {

    final byte userBytes[] = Base64.decode(userString.getBytes(), DEFAULT);
    final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(userBytes);

    final ObjectInputStream objectInputStream;
    final User user;
    try {
        objectInputStream = new ObjectInputStream(byteArrayInputStream);
        user = (User) objectInputStream.readObject();
        objectInputStream.close();
    } catch (final IOException | ClassNotFoundException exception) {
        ...
    }

    return user;
}

该对象是这样实现的:

public class User implements Serializable {
    private String email;
    private String name;

    ...
}

然后,在修改了我的对象(我添加了一个新字段)之后,我学到了一个艰难的方法,即serialVersionUID在对象定义发生变化的情况下必须设置,否则反序列化器将无法识别存储的对象(因为它将自动生成serialVersionUID)。所以我继续这样做:

public class User implements Serializable {
    private static final long serialVersionUID = 123L;

    ...
}

但是现在我已经重新发布了带有这些更改的应用程序,我不断收到错误报告,表明该对象无法反序列化:

引起:java.io.InvalidClassException: com.myproject.he; 本地类不兼容:流classdesc serialVersionUID = 184861231695454120,本地类serialVersionUID = -2021388307940757454

我非常清楚设置新的串行版本会使任何以前的串行版本(link1link2)无效,但事实并非如此。正如您所看到的,错误日志指向的(和)与我手动设置给我的班级的完全不同serialVersionUID18486...和)。-20213...User123L

我错过了什么?

如果它有任何相关性,我正在使用带有默认配置的 Proguard。

标签: javaandroiddeserializationproguardserialversionuid

解决方案


谷歌搜索了一段时间后,我偶然发现了这个问题:如何阻止 ProGuard 从类中剥离 Serializable 接口。所以我继续深入研究 APK(可以在 Android Studio中分析 APKUser ),找到我的类,并查看它的字节码:

class public Lcom/myproject/h/e;
.super Ljava/lang/Object;
.source "User.java"

# interfaces
.implements Ljava/io/Serializable;


# annotations
...


# instance fields
.field private a:Ljava/lang/String;

.field private b:Ljava/lang/String;

...


# direct methods
...

如您所见,静态字段serialVersionUID无处可寻。因此,我继续按照文档的建议添加了以下配置:

[应用程序] 可能包含已序列化的类。根据它们的使用方式,它们可能需要特别注意。[...] 有时,序列化的数据会被存储,并在稍后读回到可序列化类的更新版本中。然后必须注意这些类与其未处理的版本和未来的处理版本保持兼容。在这种情况下,相关类很可能具有serialVersionUID字段。那么以下选项应该足以确保随着时间的推移保持兼容性:

-keepnames class * implements java.io.Serializable`

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

因此,如果我转到新生成的 APK 并检查我的对象的字节码,它现在指定serialVersionUID不要丢弃或重命名:

class public Lcom/myproject/model/User;
.super Ljava/lang/Object;
.source "User.java"

...

# static fields
.field private static final serialVersionUID:J


# instance fields
...

我希望该应用程序在反序列化我的对象时不会再遇到任何问题。


推荐阅读