即使设置了serialVersionUID,反序列化也会引发InvalidClassException

时间:2019-05-19 07:40:53

标签: java android deserialization android-proguard serialversionuid

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

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.h.e;本地类不兼容:流classdesc serialVersionUID = 184861231695454120,本地类serialVersionUID = -2021388307940757454

我非常清楚,设置新的串行版本将使任何先前的串行版本(link1link2)都无效,但是事实并非如此。如您所见,错误日志指向与我为serialVersionUID类手动设置的日志完全不同的User

我想念什么?

如果有任何相关性,我将使用默认配置的Proguard。

1 个答案:

答案 0 :(得分:1)

搜索了一段时间后,我偶然发现了这个问题:How to stop ProGuard from stripping the Serializable interface from a class。因此,我继续研究APK(Android Studio中可以analyze the APK定位),找到了User类,并查看了其字节码:

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。因此,我继续将以下配置添加为documentation suggests

  

[应用程序]可能包含序列化的类。根据使用方式的不同,可能需要特别注意。 [...]有时,存储序列化的数据,稍后再读回可序列化类的更新版本。然后必须注意这些类与它们的未处理版本和将来的处理版本保持兼容。在这种情况下,相关类将很可能具有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
...

我希望该应用程序在反序列化对象方面不会再遇到任何麻烦。