AssertionError in Gson EnumTypeAdapter bei Verwendung von Proguard Obfuscation

73

Mein Projekt implementiert ein TypeAdapterIn Gsonwährend der Serialisierung / Deserialisierung, um den Polymorphismuszustand des Objekts zu erhalten. Wie auch immer, das Projekt funktioniert gut während der Entwicklungstests, aber wenn es mit Proguard-Verschleierung veröffentlicht und getestet wird, stürzt es einfach ab.

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)

Meine Gson-spezifische Proguard-Konfiguration lautet:

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

Der von mir verwendete TypeAdapter lautet:

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

Ich habe viel nach diesem für Gson spezifischen Fehler gesucht, aber keine hilfreiche Antwort gefunden. Ich fand jedoch eine andere Frage mit dem ähnlichen Problem.

Jede Hilfe von der Entwickler-Community wäre dankbar.

Waqaslam
quelle
Ich wünschte, ich könnte diese Frage 100 Mal positiv bewerten. Endlich konnte ich meine App-Abstürze in der Produktion beheben und gleichzeitig ein bisschen mehr über Enums und Proguard lernen. Tolle Frage und vielen Dank an alle, die sehr gute Antworten mit Details gepostet haben. @ Eric Lafortune
Brandon

Antworten:

183

Es scheint, als müssten wir darum bitten, dass die Mitglieder der Aufzählungen erhalten bleiben. Das Hinzufügen zur Proguard-Konfigurationsdatei hat bei mir funktioniert:

-keepclassmembers enum * { *; }

Oder, wenn Sie genauer sein möchten,

-keepclassmembers enum com.your.package.** { *; }
Pandre
quelle
4
Dies sollte die Antwort sein, sie bezieht sich auf die genaue Ausnahme. Vielen Dank!
Daniel Wilson
2
Richtige Antwort, aber können Sie bitte den Grund erklären? Vielen Dank
Xiaogegexiao
Der Grund dafür ist, dass Gsons interner Adapter für Aufzählungen die Aufzählungskonstanten durchläuft (dies kann auch beim Umbenennen von Proguard zu einem anderen Problem führen Enum.values()) und diese verwendet name(), um das Feld zu finden und nach zu suchen @SerializedName. Proguard benannte die Felder jedoch um, sodass Gson sie nicht findet. -keepclassmembers enumverhindert, dass Proguard die Felder umbenennt. (Beachten Sie, dass es eine Pull-Anfrage für Gson gibt , um das Problem zu umgehen.)
Marcono1234
19

GSON löst diesen AssertionError aus, wenn Enumerationskonstanten aus JSON-Daten nicht deserialisiert werden können, und führt eine Introspektion der Felder der Enum-Klasse durch. Leider werden die Details der zugrunde liegenden NoSuchFieldException verschluckt.

Sie sollten sicherstellen, dass Sie die Namen der Aufzählungsfelder (und der Felder im Allgemeinen) beibehalten, die serialisiert werden. Standardmäßig kann ProGuard sie umbenennen oder sogar entfernen. Zum Beispiel mit einigen Platzhaltern:

-keepclassmembers class com.example.domain.** {
    <fields>;
}
Eric Lafortune
quelle
9
ist <fields>hier ein Platzhalter für den Namen der Aufzählungsfelder oder sollte es geschrieben werden , wie sie ist?
Carlos P
11
Wie besehen: <Felder>, <Methoden>, <Init>, **, * und? sind Platzhalter, die ProGuard erkennt.
Eric Lafortune
1
Ich wünschte, Gson hätte nur einen weniger anmaßenden Weg gewählt, um dasselbe zu tun. Das heißt, iterieren Sie die Felder und geben Sie den Namen heraus, anstatt davon auszugehen, dass der Name zur Laufzeit mit dem Feldnamen übereinstimmt.
Trejkaz
14

Es wird bereits empfohlen, Proguard so zu konfigurieren, dass alle mit serialisierten Objekten verbundenen Aufzählungen erhalten bleiben. Die Tatsache, dass ich alle meine Aufzählungen explizit auflisten muss, gefällt mir nicht wirklich. Diese Lösung ist schwer zu pflegen. Eine etwas bessere Lösung, die ich mir ausgedacht habe, ist die folgende.

Verwenden Sie eine leere Schnittstelle, um anzugeben, dass eine Klasse oder Aufzählung an der Gson-Serialisierung teilnimmt:

public interface GsonSerializable { }

public class MyClass implements GsonSerializable {

    public enum MyEnum implements GsonSerializable {
        enumvalue1, enumvalue2
    }

    public MyEnum mydata1;
}

Verwenden Sie eine Proguard-Konfiguration, die sowohl die Schnittstelle als auch alle Klassen / Aufzählungen enthält, die sie implementieren:

# 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

Das war's, solange Ihre Klassen und Aufzählungen die Schnittstelle verwenden, sollten Sie in Ordnung sein. Sie können das Vorhandensein dieser Schnittstelle auch in Ihren Serialisierungs- / Deserialisierungsmethoden erzwingen, damit Sie es nicht vergessen, wenn Sie später eine neue Klasse hinzufügen:

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

Auch in Ihrer Konfiguration die Zeile mit 'com.google.gson.examples.android.model. ** {*; } 'bezieht sich auf einen Google-bezogenen Beispielcode, daher halte ich dies nicht für erforderlich.

Levente Dobson
quelle
7

In meinem Fall wurde Proguard für -keepeinzelne Klassen konfiguriert, die von Gson berührt wurden. Der Fehler wurde jedoch behoben, als ich Proguard so konfigurierte, dass das Paket beibehalten wurde , in dem sich diese einzelnen Klassen befanden :

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

Nachdem ich auf dasselbe Problem gestoßen war, ging ich durch und untersuchte die resultierende dekompilierte APK. Ich glaube, das Problem hängt damit zusammen, dass ein Aufzählungstyp seine Mitglieder während der Verschleierung verliert.

Achten Sie darauf, Aufzählungen zu behalten:

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

Stellen Sie außerdem sicher, dass ALLE in GSON verwendeten Klassen beibehalten werden:

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

Siehe vollständige Konfiguration unter pastebin.com/r5Jg3yY2

Eggman87
quelle
0

Bitte überprüfen Sie folgende Dinge:

  1. Wird die Datei proguard-rules.pro im App-Verzeichnis hinzugefügt.

  2. Ist der in build.gradle (Modul: App) definierte Pfad definiert? Die Pfaddefinition ist wie folgt korrekt:

    proguardFiles getDefaultProguardFile ('proguard-android-optimize.txt'), 'proguard-rules.pro'

  3. Wenn die obigen 2 Schritte in Ordnung sind, fügen Sie bitte die folgende Zeile (Regel) in die Progaurd-Datei ein.

    -keepclassmembers enum * {*; }}

  4. Reinigen Sie das Projekt, erstellen Sie es und führen Sie es erneut aus.

Subhash Gaikwad
quelle