2013-03-21 5 views
35

Il mio progetto implementa un TypeAdapter in Gson durante la serializzazione/deserializzazione per preservare lo stato di polimorfismo dell'oggetto. Comunque, il progetto funziona bene durante i test di sviluppo, ma quando viene rilasciato con proguard offuscamento e testato, si blocca.AssertionError in Gson EnumTypeAdapter quando si utilizza Proguard 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) 

mio GSON configurazione specifica 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 ---------- 

Il TypeAdapter sto usando è:

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

ho fatto un sacco di ricerca su questo errore specifico per GSON , ma non è stato possibile trovare alcuna risposta utile. Tuttavia ho trovato another question con il problema simile.

Qualsiasi aiuto da parte della comunità di sviluppatori sarebbe apprezzato.

+0

Vorrei poter upvote questa domanda 100 volte. Alla fine sono riuscito a risolvere i crash della mia app in produzione, e allo stesso tempo a imparare un po 'di più su enum e proguard. Grande domanda e grazie a tutti coloro che hanno inviato ottime risposte con i dettagli. @Eric Lafortune – Brandon

risposta

5

Dopo aver incontrato lo stesso problema, ho esaminato l'APK risultante decompilato. Credo che il problema sia legato ad alcuni tipi di enum che perdono i suoi membri durante l'offuscamento.

Assicurarsi di mantenere le enumerazioni:

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

Inoltre - assicurarsi che tutte le classi in uso in GSON vengono mantenuti:

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

Visualizza intera configurazione @ pastebin.com/r5Jg3yY2

16

GSON lancia questo AssertionError quando non riesce a deserializzare le costanti di enumerazione dai dati JSON, eseguendo l'introspezione sui campi della classe enum. Sfortunatamente, inghiotte i dettagli del NoSuchFieldException sottostante.

È necessario assicurarsi di conservare i nomi dei campi enum (e dei campi in generale) serializzati. Per impostazione predefinita, ProGuard può rinominare o persino rimuoverli. Per esempio, con alcuni caratteri jolly:

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

è '' qui un segnaposto per i nomi dei campi enum o dovrebbe essere scritto così com'è? –

+10

As-is: , , , **, * e? sono i caratteri jolly che riconosce ProGuard. –

+0

Vorrei che Gson avrebbe usato un modo meno presuntuoso per fare la stessa cosa. Ad esempio, iterare i campi e ottenere il nome, piuttosto che assumere il nome corrisponderebbe al nome del campo in fase di esecuzione. – Trejkaz

9

è già suggerito che è necessario configurare Proguard in un modo che mantiene ogni enum relative a oggetti serializzati intatti. Non mi piace davvero il fatto di dover elencare esplicitamente tutte le mie enumerazioni, questa soluzione è difficile da mantenere. Una soluzione leggermente migliore che ho trovato è la seguente.

Usa un'interfaccia vuota per indicare che una classe o enum partecipa in GSON serializzazione:

public interface GsonSerializable { } 

public class MyClass implements GsonSerializable { 

    public enum MyEnum implements GsonSerializable { 
     enumvalue1, enumvalue2 
    } 

    public MyEnum mydata1; 
} 

Utilizzare una configurazione Proguard che mantiene sia l'interfaccia e tutte le classi/enums che implementano:

# 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 

Ecco, fintanto che le tue classi e le enumerazioni usano l'interfaccia, dovresti essere OK.Si potrebbe anche rafforzare la presenza di questa interfaccia nei vostri metodi di serializzazione/deserializzazione, in modo da non dimenticare quando si aggiunge una nuova classe successiva:

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

anche nella configurazione della linea con 'com.google.gson .examples.android.model. ** {*; } 'si riferisce ad alcuni codici di esempio relativi a Google, quindi non penso sia necessario.

4

Nel mio caso, Proguard è stato configurato per -keep lezioni individuali toccati da GSON, ma l'errore è andato via quando ho configurato Proguard per mantenere il pacchetto in cui tali singole classi risiedevano:

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

Sembra dobbiamo chiedere che i membri delle enumerazioni siano mantenuti. Questo ha funzionato per me:

-keepclassmembers enum * { *; } 

Oppure, se si vuole essere più precisi,

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

Questa dovrebbe essere la risposta, fa riferimento all'eccezione esatta. Grazie! –

+1

Pollice su (y), hai risolto il mio problema. –

+0

Risposta corretta, ma potresti gentilmente spiegare il motivo? Grazie – Xiaogegexiao