使用Proguard混淆时,Gson EnumTypeAdapter出现断言错误

80

我的项目在序列化/反序列化期间实现了 Gson 中的 TypeAdapter,以保留对象的多态状态。不过,尽管该项目在开发测试期间运行良好,但是当使用 proguard混淆 进行发布和测试时,它就会崩溃。

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)

我的Gson特定的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特定错误的信息时尝试了很多,但没有找到任何有用的答案。然而,我找到了一个类似问题的提问。

希望开发者社区能够提供帮助。


1
我希望我能给这个问题点赞100次。我终于成功解决了我的应用在生产环境中的崩溃问题,同时也学到了更多关于枚举和Proguard的知识。非常好的问题,感谢所有发表详细答案的人。@Eric Lafortune - Brandon
7个回答

206
似乎我们必须要求保留枚举的成员。将以下内容添加到proguard配置文件中对我有效:

看起来我们必须请求保留枚举的成员。在我的情况下,通过将以下内容添加到ProGuard配置文件中,问题得到了解决:

-keepclassmembers enum * { *; }

或者,如果您想更具体一些,

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

4
这应该就是答案,它涉及到了具体的例外情况。谢谢! - Daniel Wilson
2
回答正确,但您能否友好地解释一下原因?谢谢。 - Xiaogegexiao
原因是Gson的枚举内部适配器遍历枚举常量(这也可能在Proguard重命名Enum.values()时导致不同的问题),然后使用它们的name()查找字段并检查@SerializedName。但是,Proguard重命名了这些字段,因此Gson无法找到它们。-keepclassmembers enum可以防止Proguard重命名这些字段。(请注意,有一个Gson的pull请求作为解决方法) - Marcono1234

19

当GSON无法从JSON数据反序列化枚举常量时,它会抛出此AssertionError异常,并对枚举类的字段进行内省。不幸的是,它会隐藏底层的NoSuchFieldException的详细信息。

您应该确保保留被序列化的枚举字段(包括一般字段)的名称。默认情况下,ProGuard可能会重命名或甚至删除它们。例如,使用一些通配符:

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

9
这里的“<fields>”是枚举字段名称的占位符,还是应该按原样书写? - Carlos P
11
<fields>、<methods>、<init>、* 和 ? 是 ProGuard 可识别的通配符。 - Eric Lafortune
1
我希望Gson能够使用一种不那么自以为是的方式来完成同样的事情。即,迭代字段并获取名称,而不是假设名称将在运行时与字段名称匹配。 - Hakanai

15
已经建议您以一种使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相关的示例代码,所以我认为这并不必要。


7
在我的情况下,proguard被配置为-keep保留Gson所涉及的个别类,但当我将proguard配置为保留这些个别类所在的package时,错误消失了。
-keep class com.company.library.model.** { *; }

5

在遇到同样的问题后,我仔细检查了反编译的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。

1

在枚举类上应用androidx.annotation.Keep注解。例如:

@Keep
enum class PlayerType {
    PRO,
    INTERMEDIATE,
    BASIC
}

0
请确认以下事项 -
  1. 在应用程序目录中是否添加了proguard-rules.pro文件。

  2. 在build.gradle(module:app)文件中,路径定义是否正确,如下所示 -

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

  3. 如果上述两个步骤都没问题,请在progaurd文件中添加以下行(rule)-

    -keepclassmembers enum * { *; }

  4. 清理、构建项目并重新运行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接