使用 EnumTypeAdapter 时,将 proguard 与 GSON 和 RoboGuice 一起使用会失败

Posted

技术标签:

【中文标题】使用 EnumTypeAdapter 时,将 proguard 与 GSON 和 RoboGuice 一起使用会失败【英文标题】:Using proguard with GSON and RoboGuice fails when using a EnumTypeAdapter 【发布时间】:2013-03-07 04:40:06 【问题描述】:

当尝试将 EnumTypeAdapter 与 Proguard 一起使用时,会导致 Gson.getAdapter() 中出现 AssertionError。这个错误似乎是由于类型信息丢失......下面是所有相关的源代码:

例外:

03-18 13:27:12.905: ERROR/roboguice(12139): Throwable caught during background processing
    java.lang.AssertionError
    at com.google.gson.internal.bind.TypeAdapters$EnumTypeAdapter.<init>(Unknown Source)
    at com.google.gson.internal.bind.TypeAdapters$24.create(Unknown Source)
    at com.google.gson.Gson.getAdapter(Unknown Source)

正在使用的 EnumTypeAdapter:

public class OperationResultSerializer implements JsonDeserializer<OperationResult>, JsonSerializer<OperationResult> 

@Override
public OperationResult deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException 
    int value = json.getAsJsonPrimitive().getAsInt();
    return OperationResult.values()[value];


@Override
public JsonElement serialize(OperationResult src, Type typeOfSrc, JsonSerializationContext context) 
    return new JsonPrimitive(src.ordinal());


我如何构建我的 GSON 对象:

            gson = new GsonBuilder()
                .registerTypeAdapter(Calendar.class, new WcfCalendarSerializer())
                .registerTypeAdapter(OperationResult.class, new OperationResultSerializer())
                .registerTypeAdapter(FieldName.class, new FieldNameSerializer())
                .registerTypeAdapter(MealType.class, new MealTypeSerializer())
                .create();

我的 ProGuard 配置:

#-dontusemixedcaseclassnames: Necessary when building on windows where x.class and X.class is the same file
-dontusemixedcaseclassnames

-keepattributes *Annotation*
-keepattributes Signature

# Preserve the special static methods that are required in all enumeration classes.
-keepclassmembers enum * 
    public static **[] values();
    public static ** valueOf(java.lang.String);


-keep public class * extends android.app.Application
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep class * extends android.view.View  
  public <init>(android.content.Context); 
  public <init>(android.content.Context, android.util.AttributeSet); 
  public <init>(android.content.Context, android.util.AttributeSet, int); 
  public void set*(...); 

-keep class com.google.inject.**  *;  
-keep class javax.inject.**  *;  
-keep class javax.annotation.**  *;  
-keep class roboguice.**  *;  

-keep class * extends android.preference.Preference  
  public <init>(android.content.Context); 
  public <init>(android.content.Context, android.util.AttributeSet); 
  public <init>(android.content.Context, android.util.AttributeSet, int); 
  public void set*(...); 
   

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

###Action bar sherlock 
-keep class android.support.v4.app.**  *;  
-keep interface android.support.v4.app.**  *;  
-keep class com.actionbarsherlock.**  *;  
-keep interface com.actionbarsherlock.**  *;  

###RoboGuice 
-keepclassmembers class *  
    @com.google.inject.Inject <init>(...); 
 
-keepclassmembers class *  
    void *(**On*Event); 
 
-keep public class roboguice.** 
-keep class com.google.inject.** 
-keep class com.google.gson.** *;

#datacontract
-keep public class com.ordering.datacontract.*
-keepclassmembers class com.ordering.datacontract.*

# LVL License binder class
-keep class com.android.vending.licensing.ILicensingService    


-dontwarn
-ignorewarnings

如前所述,我怀疑由于类型信息丢失而导致某些事情失败 - 在深入研究 GSON 源代码之后,这是为解析 EnumTypeAdapter 而调用的代码......显然 getEnumConstants 正在返回一个名称不作为 classOfT 类型的字段存在。但我不确定这怎么可能。

private static final class EnumTypeAdapter<T extends Enum<T>> extends TypeAdapter<T> 
private final Map<String, T> nameToConstant = new HashMap<String, T>();
private final Map<T, String> constantToName = new HashMap<T, String>();

public EnumTypeAdapter(Class<T> classOfT) 
  try 
    for (T constant : classOfT.getEnumConstants()) 
      String name = constant.name();
      SerializedName annotation = classOfT.getField(name).getAnnotation(SerializedName.class);
      if (annotation != null) 
        name = annotation.value();
      
      nameToConstant.put(name, constant);
      constantToName.put(constant, name);
    
   catch (NoSuchFieldException e) 
    throw new AssertionError();
  

public T read(JsonReader in) throws IOException 
  if (in.peek() == JsonToken.NULL) 
    in.nextNull();
    return null;
  
  return nameToConstant.get(in.nextString());


public void write(JsonWriter out, T value) throws IOException 
  out.value(value == null ? null : constantToName.get(value));

  

我已经搜索了所有可能的解决方案,但没有找到太多帮助。也许有人以前遇到过这种情况并可以指出我正确的方向?

【问题讨论】:

【参考方案1】:

我检查并检查了反编译的 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

【讨论】:

这对我不起作用,最终重构了我正在使用的一个枚举。

以上是关于使用 EnumTypeAdapter 时,将 proguard 与 GSON 和 RoboGuice 一起使用会失败的主要内容,如果未能解决你的问题,请参考以下文章

Jmeter 导入Eclipse,如何编译

当在VSTS中更新目标分支时,是否可以获得PR以重新启动合并?

GitHub - 使用 PR 中所有提交的语义发布版本说明

仅当有人打开 PR 时才运行 GitHub Action

剪辑视频时PR播放卡顿不连贯|如何修复Premiere软件中播放太卡问题

当某些文件更改时,自动请求更改 Github 上的 PR