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

cav*_*llo 2 java android proguard serialversionuid deserialization

不久前,我发布了一个序列化/反序列化用户对象的应用程序。

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;
}
Run Code Online (Sandbox Code Playgroud)

该对象是这样实现的:

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

    ...
}

Run Code Online (Sandbox Code Playgroud)

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

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

    ...
}

Run Code Online (Sandbox Code Playgroud)

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

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

我非常清楚设置新的串行版本将使任何以前的串行版本(link1link2)无效,但事实并非如此。正如您所看到的,错误日志指向的serialVersionUID(18486...-20213...) 与我手动设置到我的User类 ( 123L) 的错误日志完全不同。

我缺少什么?

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

cav*_*llo 5

经过谷歌搜索很长一段时间后,我偶然发现了这个问题:How to stop ProGuard from stripping the Serialized interface from a class。因此,我继续深入研究 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
...
Run Code Online (Sandbox Code Playgroud)

如您所见,serialVersionUID找不到 Static 字段。所以我继续按照文档的建议添加了以下配置:

[应用程序] 可能包含序列化的类。根据它们的使用方式,可能需要特别注意。[...]有时,序列化的数据会被存储,并在稍后读回到新版本的可序列化类中。然后,必须注意这些类与其未处理的版本以及未来处理的版本保持兼容。在这种情况下,相关类很可能有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();
}
Run Code Online (Sandbox Code Playgroud)

因此,如果我转到新生成的 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
...
Run Code Online (Sandbox Code Playgroud)

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