You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

开启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'
    }
}

排查过程

  1. 数据库存储内容验证:在转换器convertToEntityProperty方法中打印数据库存储的JSON字符串,内容正常,枚举值以字符串形式正确保存:
    ListeTypeEtDirectionMouvementConverter: [{"first":"DeplacementYPuisX","second":{"directionMouvementHorizontal":"VersLaGauche","directionMouvementVertical":"VersLeBas","valeurPourBDD":11}},{"first":"DeplacementXPuisY","second":{"directionMouvementHorizontal":"VersLaGauche","directionMouvementVertical":"VersLeBas","valeurPourBDD":11}}]
    
  2. 对象类型验证:直接打印infos.first时输出正常,但调用infos.first.name()或toString()时触发ClassCastException,说明infos.first实际是String类型,而非TypeMouvement枚举实例。
  3. 转换器返回值确认:进一步调试发现,转换器返回的列表中,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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 09:08:10