开启R8优化后Android应用触发ClassCastException崩溃问题
Android启用R8代码优化与混淆后触发ClassCastException崩溃
崩溃堆栈信息
FATAL EXCEPTION: StickyMoteurThread Process: com.tentaclestudio.fantasyracing.debug, PID: 11086 java.lang.ClassCastException: java.lang.String cannot be cast to com.tentaclestudio.fantasyracing.interfacesMoteurs.mouvements.TypeMouvement at com.tentaclestudio.fantasyracing.interfacesMoteurs.donneesparties.CaseSelectionnableInformations.toJSON(SourceFile:613) at com.tentaclestudio.fantasyracing.interfacesMoteurs.sortiesMoteurJeu.ResultatStepJeu.toJSON(SourceFile:1583) at com.tentaclestudio.fantasyracing.moteurdejeu.StickyPartieDebugInfo.<init>(SourceFile:255) at com.tentaclestudio.fantasyracing.moteurdejeu.StickyPartie.addDebugInfos(SourceFile:3555) at com.tentaclestudio.fantasyracing.moteurdejeu.CalculateurStepDeJeu.jouerStep(SourceFile:1606) at com.tentaclestudio.fantasyracing.moteurdejeu.StickyMoteur.jouerStep(SourceFile:1814) at com.tentaclestudio.fantasyracing.moteurdejeu.SynchronisateurPartieEnLigne.jouerPartieJusquaInteraction(SourceFile:850) at com.tentaclestudio.fantasyracing.moteurdejeu.SynchronisateurPartieEnLigne.synchroniserPartieEnLigne(SourceFile:1020) at com.tentaclestudio.fantasyracing.moteurdejeu.StickyMoteur.synchroniserPartieEnLigne(SourceFile:1465) at com.tentaclestudio.fantasyracing.moteurdejeu.StickyMoteur.synchroniserDebutDePartie(SourceFile:1227) at com.tentaclestudio.fantasyracing.moteurdejeu.StickyMoteurHandler.handleMessage(SourceFile:61) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loopOnce(Looper.java:230)
相关代码片段
崩溃触发代码
JSONObject mvtAutorises = new JSONObject(); if (listeMouvementsAutorises != null) { for (int i = 0; i < listeMouvementsAutorises.size(); i++) { Pair<TypeMouvement, DirectionMouvement> infos = listeMouvementsAutorises.get(i); mvtAutorises.put("mvtAutorise_" + i, infos.first.name() + " - " + infos.second.toString()); // <=== 崩溃位置 } }
ObjectBox实体属性定义
@Convert(converter = ListeTypeEtDirectionMouvementConverter.class, dbType = String.class) List<Pair<TypeMouvement, DirectionMouvement>> listeMouvementsAutorises;
ObjectBox转换器实现
public static class ListeTypeEtDirectionMouvementConverter implements PropertyConverter<List<Pair<TypeMouvement, DirectionMouvement>>, String> { @Override public List<Pair<TypeMouvement, DirectionMouvement>> convertToEntityProperty(String databaseValue) { Type typeListe = new TypeToken<List<Pair<TypeMouvement, DirectionMouvement>>>(){}.getType(); Gson gson = new Gson(); List<Pair<TypeMouvement, DirectionMouvement>> listeresultat = gson.fromJson(databaseValue, typeListe); return listeresultat; } @Override public String convertToDatabaseValue(List<Pair<TypeMouvement, DirectionMouvement>> entityProperty) { Type typeListe = new TypeToken<List<Pair<TypeMouvement, DirectionMouvement>>>(){}.getType(); Gson gson = new Gson(); return gson.toJson(entityProperty, typeListe); } }
当前混淆与构建配置
proguard-rules.pro 完整内容
-printconfiguration /home/julien/Desktop/Brouillons/full-r8-config.txt -dontwarn org.bouncycastle.jsse.BCSSLParameters -dontwarn org.bouncycastle.jsse.BCSSLSocket -dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider -dontwarn org.conscrypt.Conscrypt$Version -dontwarn org.conscrypt.Conscrypt -dontwarn org.conscrypt.ConscryptHostnameVerifier -dontwarn org.openjsse.javax.net.ssl.SSLParameters -dontwarn org.openjsse.javax.net.ssl.SSLSocket -dontwarn org.openjsse.net.ssl.OpenJSSE ##---------------Begin: proguard configuration for Gson ---------- -keepattributes Signature -keepattributes *Annotation* -dontwarn sun.misc.** -keep class com.google.gson.examples.android.model.** { <fields>; } -keep class * extends com.google.gson.TypeAdapter -keep class * implements com.google.gson.TypeAdapterFactory -keep class * implements com.google.gson.JsonSerializer -keep class * implements com.google.gson.JsonDeserializer -keepclassmembers,allowobfuscation class * { @com.google.gson.annotations.SerializedName <fields>; } -keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken -keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken ##---------------End: proguard configuration for Gson ---------- # For enumeration classes -keepclassmembers,allowoptimization enum * { public static **[] values(); public static ** valueOf(java.lang.String); } -keep enum com.tentaclestudio.fantasyracing.interfacesMoteurs.mouvements.TypeMouvement { *; } # Keep native method names -keepclasseswithmembers,includedescriptorclasses,allowshrinking class * { native <methods>; }
build.gradle 构建类型配置
buildTypes { release { minifyEnabled true shrinkResources false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' ndk.debugSymbolLevel "SYMBOL_TABLE" } debug { minifyEnabled true shrinkResources false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' applicationIdSuffix ".debug" debuggable true versionNameSuffix '.debug' } }
排查过程
- 数据库存储内容验证:在转换器
convertToEntityProperty方法中打印数据库存储的JSON字符串,内容正常,枚举值以字符串形式正确保存:ListeTypeEtDirectionMouvementConverter: [{"first":"DeplacementYPuisX","second":{"directionMouvementHorizontal":"VersLaGauche","directionMouvementVertical":"VersLeBas","valeurPourBDD":11}},{"first":"DeplacementXPuisY","second":{"directionMouvementHorizontal":"VersLaGauche","directionMouvementVertical":"VersLeBas","valeurPourBDD":11}}] - 对象类型验证:直接打印
infos.first时输出正常,但调用infos.first.name()或toString()时触发ClassCastException,说明infos.first实际是String类型,而非TypeMouvement枚举实例。 - 转换器返回值确认:进一步调试发现,转换器返回的列表中,Pair的first和second元素并非枚举实例,而是对应的字符串值。
问题根源
R8的优化导致Gson无法正确识别TypeToken中声明的枚举类型,反序列化时没有将字符串转换为枚举实例,而是直接保留字符串类型。虽然已添加枚举相关的混淆规则,但R8可能对TypeToken的匿名子类进行了优化,导致Gson无法获取准确的泛型类型信息。
解决方案
1. 增强枚举与TypeToken的混淆保护
在proguard-rules.pro中添加以下规则,确保枚举类的完整结构和TypeToken的泛型信息不被破坏:
# 保留枚举类的所有成员及类型信息 -keep class com.tentaclestudio.fantasyracing.interfacesMoteurs.mouvements.TypeMouvement { public static final ** *; } -keep class com.tentaclestudio.fantasyracing.interfacesMoteurs.mouvements.DirectionMouvement { public static final ** *; } # 禁止R8优化TypeToken的匿名子类,确保泛型类型信息保留 -keepclassmembers class * extends com.google.gson.reflect.TypeToken { <init>(...); }
2. 自定义Gson枚举TypeAdapter
为枚举类编写自定义TypeAdapter,强制Gson在反序列化时将字符串转换为枚举实例,避免依赖自动类型推断:
public class EnumTypeAdapter<T extends Enum<T>> extends TypeAdapter<T> { private final Class<T> enumClass; public EnumTypeAdapter(Class<T> enumClass) { this.enumClass = enumClass; } @Override public void write(JsonWriter out, T value) throws IOException { out.value(value.name()); } @Override public T read(JsonReader in) throws IOException { return Enum.valueOf(enumClass, in.nextString()); } }
然后在转换器中使用配置好的Gson实例:
@Override public List<Pair<TypeMouvement, DirectionMouvement>> convertToEntityProperty(String databaseValue) { Type typeListe = new TypeToken<List<Pair<TypeMouvement, DirectionMouvement>>>(){}.getType(); Gson gson = new GsonBuilder() .registerTypeAdapter(TypeMouvement.class, new EnumTypeAdapter<>(TypeMouvement.class)) .registerTypeAdapter(DirectionMouvement.class, new EnumTypeAdapter<>(DirectionMouvement.class)) .create(); List<Pair<TypeMouvement, DirectionMouvement>> listeresultat = gson.fromJson(databaseValue, typeListe); return listeresultat; }
3. 禁用R8的泛型类型优化
在gradle.properties中添加以下配置,禁止R8移除泛型类型信息:
android.enableR8.fullMode=false
(注:此方法会降低优化程度,建议优先使用前两种方案)
内容的提问来源于stack exchange,提问作者Julien
相关产品推荐
相关产品推荐

