Подтвердить что ты не робот

AssertionError в Gson EnumTypeAdapter при использовании Obfuscation Proguard

Мой проект реализует TypeAdapter в Gson во время сериализации/десериализации для сохранения состояния полиморфизма объекта. Во всяком случае, проект отлично работает во время тестов разработки, но когда он выпущен с profard obfuscation и протестирован, он просто сработает.

03-21 10:06:53.632: E/AndroidRuntime(12441): FATAL EXCEPTION: main
03-21 10:06:53.632: E/AndroidRuntime(12441): java.lang.AssertionError
03-21 10:06:53.632: E/AndroidRuntime(12441):    at com.google.gson.internal.bind.TypeAdapters$EnumTypeAdapter.<init>(SourceFile:724)
03-21 10:06:53.632: E/AndroidRuntime(12441):    at com.google.gson.internal.bind.TypeAdapters$26.create(SourceFile:753)
03-21 10:06:53.632: E/AndroidRuntime(12441):    at com.google.gson.Gson.getAdapter(SourceFile:353)
03-21 10:06:53.632: E/AndroidRuntime(12441):    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.<init>(SourceFile:82)
03-21 10:06:53.632: E/AndroidRuntime(12441):    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(SourceFile:81)
03-21 10:06:53.632: E/AndroidRuntime(12441):    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(SourceFile:118)
03-21 10:06:53.632: E/AndroidRuntime(12441):    at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(SourceFile:72)
03-21 10:06:53.632: E/AndroidRuntime(12441):    at com.google.gson.Gson.getAdapter(SourceFile:353)
03-21 10:06:53.632: E/AndroidRuntime(12441):    at com.google.gson.Gson.toJson(SourceFile:578)
03-21 10:06:53.632: E/AndroidRuntime(12441):    at com.google.gson.Gson.toJsonTree(SourceFile:479)
03-21 10:06:53.632: E/AndroidRuntime(12441):    at com.google.gson.Gson.toJsonTree(SourceFile:458)
03-21 10:06:53.632: E/AndroidRuntime(12441):    at com.google.gson.Gson$3.serialize(SourceFile:137)

Конфигурация MyGson специфическая proguard:

##---------------Begin: proguard configuration for Gson  ----------
# Gson uses generic type information stored in a class file when working with fields. Proguard
# removes such information by default, so configure it to keep all of it.
-keepattributes Signature

# For using GSON @Expose annotation
-keepattributes *Annotation*

# Gson specific classes
-keep class sun.misc.Unsafe { *; }
#-keep class com.google.gson.stream.** { *; }

# Application classes that will be serialized/deserialized over Gson
-keep class com.google.gson.examples.android.model.** { *; }

#This is extra - added by me to exclude gson obfuscation
-keep class com.google.gson.** { *; }

##---------------End: proguard configuration for Gson  ----------

TypeAdapter, который я использую:

public final class GsonWorkshiftAdapter implements JsonSerializer<IWorkshift>, JsonDeserializer<IWorkshift> {
    private static final String CLASSNAME = "CLASSNAME";
    private static final String INSTANCE  = "INSTANCE";

    @Override
    public JsonElement serialize(IWorkshift src, Type typeOfSrc, JsonSerializationContext context) {
        String className = src.getClass().getCanonicalName();
        JsonElement elem = context.serialize(src);

        JsonObject retValue = new JsonObject();
        retValue.addProperty(CLASSNAME, className);
        retValue.add(INSTANCE, elem);

        return retValue;
    }

    @Override
    public IWorkshift deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        JsonObject jsonObject =  json.getAsJsonObject();
        JsonPrimitive prim = (JsonPrimitive) jsonObject.get(CLASSNAME);
        String className = prim.getAsString();

        Class<?> klass = null;
        try { klass = Class.forName(className); }
        catch (ClassNotFoundException e) { throw new JsonParseException(e.getMessage()); }

        return context.deserialize(jsonObject.get(INSTANCE), klass);
    }
}

Я очень много искал эту ошибку, специфичную для Gson, но не нашел полезного ответа. Однако я нашел еще один вопрос с аналогичной проблемой.

Любая помощь сообщества разработчиков будет оценена.

4b9b3361

Ответ 1

Похоже, мы должны просить, чтобы члены перечислений были сохранены. Это сработало для меня:

-keepclassmembers enum * { *; }

Или, если вы хотите быть более конкретным,

-keepclassmembers enum com.your.package.** { *; }

Ответ 2

GSON выбрасывает этот AssertionError, когда ему не удается десериализовать константы перечисления из данных JSON, выполняя интроспекцию в полях класса enum. К сожалению, он проглатывает детали лежащего в основе исключения NoSuchFieldException.

Вы должны убедиться, что вы сохранили имена полей enum (и полей в целом), которые сериализованы. По умолчанию ProGuard может переименовывать или даже удалять их. Например, с некоторыми подстановочными знаками:

-keepclassmembers class com.example.domain.** {
    <fields>;
}

Ответ 3

Уже было предложено настроить Proguard таким образом, чтобы он сохранял все перечисления, связанные с сериализованными объектами. Мне не очень нравится тот факт, что я должен явно перечислять все мои перечисления, это решение сложно поддерживать. Немного лучшее решение, которое я придумал, следующее.

Используйте пустой интерфейс, чтобы указать, что класс или перечисление принимает участие в сериализации Gson:

public interface GsonSerializable { }

public class MyClass implements GsonSerializable {

    public enum MyEnum implements GsonSerializable {
        enumvalue1, enumvalue2
    }

    public MyEnum mydata1;
}

Используйте конфигурацию Proguard, которая поддерживает как интерфейс, так и все классы/перечисления, которые его реализуют:

# keep GsonSerializable interface, it would be thrown away by proguard since it is empty
-keep class com.example.GsonSerializable

# member fields of serialized classes, including enums that implement this interface
-keepclassmembers class * implements com.example.GsonSerializable {
    <fields>;
}

# also keep names of these classes. not required, but just in case.
-keepnames class * implements com.example.GsonSerializable

Чтобы это, пока ваши классы и перечисления используют интерфейс, вы должны быть в порядке. Вы также можете обеспечить присутствие этого интерфейса в методах сериализации/десериализации, поэтому не забывайте при добавлении нового класса позже:

public String serializeWithGson(GsonSerializable object) { ... }

Также в вашей конфигурации строка с 'com.google.gson.examples.android.model. ** {*; } 'относится к некоторому примерному коду Google, поэтому я не думаю, что это необходимо.

Ответ 4

После того, как я столкнулся с той же проблемой, я просмотрел полученный APK, декомпилированный. Я полагаю, что проблема связана с тем, что некоторые типы перечислений теряют свои члены во время обфускации.

Обязательно сохраняйте перечисления:

 -keepclassmembers enum * {
     public static **[] values();
     public static ** valueOf(java.lang.String);
 }

Также - убедитесь, что ВСЕ классы, используемые в GSON, сохраняются:

 -keep public class com.company.ordering.datacontract.** {
     public protected *;
 }

 -keep public class com.company.ordering.service.request.** {
     public protected *;
 }
 -keep public class com.company.ordering.service.response.** {
     public protected *;
 }

Смотрите полную конфигурацию @pastebin.com/r5Jg3yY2

Ответ 5

В моем случае proguard был настроен на -keep отдельные классы, затронутые Gson, но ошибка ушла, когда я настроил proguard, чтобы сохранить пакет, в котором были эти отдельные классы:

-keep class com.company.library.model.** { *; }