Десериализация вызывает InvalidClassException, даже если установлен serialVersionUID - PullRequest
1 голос
/ 19 мая 2019

Некоторое время назад я опубликовал приложение, которое сериализовало / десериализовало объект пользователя.

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;несовместимый локальный класс: stream classdesc serialVersionUID = 184861231695454120, локальный класс serialVersionUID = -2021388307940757454

Мне очень известно, что установка новой последовательной версии сделает недействительной любую предыдущую последовательную версию ( link1 ), link2 ), но это не так.Как вы можете видеть, журнал ошибок указывает на совершенно другое serialVersionUID, чем то, которое я вручную установил для своего User класса.

Чего мне не хватает?

Если оно какого-либоактуальность, я использую Proguard с настройками по умолчанию.

1 Ответ

1 голос
/ 19 мая 2019

После долгого поиска в Google я наткнулся на вопрос: Как не дать ProGuard удалить интерфейс Serializable из класса .Поэтому я пошел дальше и углубился в APK (можно проанализировать APK в Android Studio), обнаружил мой класс 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 нигде не найдено.Поэтому я добавил следующую конфигурацию, поскольку в документации предлагается :

[Приложения] могут содержать классы, которые сериализуются.В зависимости от того, как они используются, они могут требовать особого внимания.[...] Иногда сериализованные данные сохраняются и позже считываются в более новые версии сериализуемых классов.Затем необходимо позаботиться о том, чтобы классы оставались совместимыми с их необработанными версиями и будущими обработанными версиями.В таких случаях соответствующие классы, скорее всего, будут иметь поля 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
...

Я бы ожидал, что у приложения больше не будет проблем с десериализацией моего объекта.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...