Reputation: 4318
Some time ago I published an app that serialized/deserialized an user object.
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;
}
The object was implemented this way:
public class User implements Serializable {
private String email;
private String name;
...
}
Then, after modifying my object (I added a new field), I learned the hard way that one has to set the serialVersionUID
in case the object definition ever changes, otherwise the deserializer won't be able to recognize the stored object (as it will autogenerate the serialVersionUID
). So I went ahead and did just that:
public class User implements Serializable {
private static final long serialVersionUID = 123L;
...
}
But now that I've republished the app with these changes, I keep getting error reports indicating the object could not be deserialized:
Caused by: java.io.InvalidClassException: com.myproject.h.e; local class incompatible: stream classdesc serialVersionUID = 184861231695454120, local class serialVersionUID = -2021388307940757454
I'm very aware that setting a new serial version would invalidate any previous serial version (link1, link2), but this isn't the case. As you can see the error log points to a totally different serialVersionUID
(18486...
and -20213...
) than the one I manually set to my User
class (123L
).
What am I missing?
If it is of any relevance, I'm using Proguard with the default configs.
Upvotes: 2
Views: 1187
Reputation: 4318
After googling for quite some time, I stumbled upon this question: How to stop ProGuard from stripping the Serializable interface from a class. So I went ahead and dove into the APK (one can analyze the APK in Android Studio), located my User
class, and took a look at its Bytecode:
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
...
As you can see, the Static field serialVersionUID
is nowhere to be found. So I went ahead and added the following configuration as the documentation suggests:
[Applications] may contain classes that are serialized. Depending on the way in which they are used, they may require special attention. [...] Sometimes, the serialized data are stored, and read back later into newer versions of the serializable classes. One then has to take care the classes remain compatible with their unprocessed versions and with future processed versions. In such cases, the relevant classes will most likely have
serialVersionUID
fields. The following options should then be sufficient to ensure compatibility over time:
-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();
}
So, if I go to the newly generated APK and check my object's bytecode, it now specifies that the serialVersionUID
is not to be discarded or renamed:
class public Lcom/myproject/model/User;
.super Ljava/lang/Object;
.source "User.java"
...
# static fields
.field private static final serialVersionUID:J
# instance fields
...
I would expect the app not to have any trouble deserializing my object anymore.
Upvotes: 5