web-dev-qa-db-fra.com

AssertionError dans Gson EnumTypeAdapter lors de l'utilisation de Proguard Obfuscation

Mon projet implémente un TypeAdapter dans Gson pendant la sérialisation/désérialisation pour préserver l'état de polymorphisme de l'objet. Quoi qu'il en soit, le projet fonctionne bien pendant les tests de développement, mais lorsqu'il est publié avec l'obscurcissement proguard et testé, il se bloque simplement.

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)

Ma configuration de proguard spécifique à Gson est:

##---------------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  ----------

Le TypeAdapter que j'utilise est:

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);
    }
}

J'ai fait beaucoup de recherches sur cette erreur spécifique à Gson, mais je n'ai trouvé aucune réponse utile. Cependant, j'ai trouvé ne autre question avec le même problème.

Toute aide de la communauté des développeurs serait appréciée.

61
waqaslam

Il semble que nous devons demander que les membres des énumérations soient conservés. L'ajout de cela au fichier de configuration proguard a fonctionné pour moi:

-keepclassmembers enum * { *; }

Ou, si vous voulez être plus précis,

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

GSON lève cette AssertionError lorsqu'il ne parvient pas à désérialiser les constantes d'énumération des données JSON, effectuant une introspection sur les champs de la classe enum. Malheureusement, il avale les détails de l'exception NoSuchFieldException sous-jacente.

Vous devez vous assurer que vous conservez les noms des champs d'énumération (et des champs en général) qui sont sérialisés. Par défaut, ProGuard peut les renommer ou même les supprimer. Par exemple, avec certains caractères génériques:

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

Il est déjà suggéré que vous devez configurer Proguard de manière à ce que toutes les énumérations liées aux objets sérialisés restent intactes. Je n'aime pas vraiment le fait que je doive lister explicitement toutes mes énumérations, cette solution est difficile à maintenir. Une solution légèrement meilleure que j'ai trouvée est la suivante.

Utilisez une interface vide pour indiquer qu'une classe ou une énumération participe à la sérialisation Gson:

public interface GsonSerializable { }

public class MyClass implements GsonSerializable {

    public enum MyEnum implements GsonSerializable {
        enumvalue1, enumvalue2
    }

    public MyEnum mydata1;
}

Utilisez une configuration Proguard qui conserve à la fois l'interface et toutes les classes/énumérations qui l'implémentent:

# 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

Voilà, tant que vos classes et énumérations utilisent l'interface, vous devriez être OK. Vous pouvez également imposer la présence de cette interface dans vos méthodes de sérialisation/désérialisation, afin de ne pas l'oublier lors de l'ajout d'une nouvelle classe plus tard:

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

Toujours dans votre configuration, la ligne avec 'com.google.gson.examples.Android.model. ** {*; } "fait référence à un exemple de code associé à Google, donc je ne pense pas que ce soit nécessaire.

12
Levente Dobson

Après avoir rencontré le même problème, j'ai examiné et examiné l'APK résultant décompilé. Je crois que le problème est lié à la perte de certains membres du type enum pendant l'obfuscation.

Assurez-vous de garder les énumérations:

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

Assurez-vous également que TOUTES les classes utilisées dans GSON sont conservées:

 -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 *;
 }

Voir la configuration complète @ Pastebin.com/r5Jg3yY2

5
Eggman87

Dans mon cas, proguard a été configuré sur -keep classes individuelles touchées par Gson, mais l'erreur a disparu quand j'ai configuré proguard pour conserver package où ces classes individuelles résidaient:

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