3 支付核心类的keep规则设计:保护支付接口、回调、实体类不被混淆破坏

3.1 原理:混淆对支付代码的破坏风险

支付SDK与外部调用方、支付网关之间通过接口契约、回调通知和数据实体进行通信。混淆默认会重命名类名、方法名和字段名,一旦支付接口的方法签名被篡改,外部调用将找不到对应方法;回调类的方法名改变会导致支付结果无法送达;实体类的字段名变化会破坏序列化/反序列化流程,轻则数据丢失,重则运行时崩溃。因此必须为支付核心类设计精确的keep规则,锁定契约边界。

支付核心类Keep规则架构 支付核心类Keep规则 支付接口 回调类 实体类 方法签名 参数类型 onSuccess onFailure 字段保护 序列化ID 说明:从顶层到底层逐级细化Keep规则,锁定支付接口、回调、实体类的契约边界 避免混淆重命名破坏支付链路的运行时正确性

3.2 支付接口保护规则

支付接口定义了与外部调用方的通信契约,接口中的每一个方法签名都必须被完整保留。混淆后如果方法名或参数类型发生变化,调用方将无法通过反射或直接调用找到目标方法。针对支付接口,使用 -keep interface 锁定所有 public 方法。

# ============================================
# 支付接口 keep 规则
# 保护 IPaymentChannel 及其所有 public 方法
# ============================================
-keep interface com.payment.sdk.IPaymentChannel {
    public *;
}

# 若接口包含泛型参数或注解,一并保留
-keep,allowobfuscation interface com.payment.sdk.IPaymentChannel

# 保留接口的默认方法(Java 8+)
-keepclassmembers interface com.payment.sdk.IPaymentChannel {
    default *;
}
最佳实践:支付接口建议使用 @Keep 注解 + ProGuard 规则双重保险。如果团队内使用了 @Keep 注解,ProGuard 规则中仍应显式写出 keep 指令,避免因注解处理器版本差异导致遗漏。

3.3 回调类保护规则

支付回调类通常由支付网关在下单、退款、对账等流程结束后触发。回调方法(如 onSuccess、onFailure、onCancel)的名称和参数类型必须与支付网关的约定完全一致。混淆重命名会直接导致回调无法被正确识别,造成支付结果丢失或订单状态不一致。

# ============================================
# 回调类 keep 规则
# 保护 PaymentCallback 及其所有回调方法
# ============================================
-keep class com.payment.sdk.PaymentCallback {
    public void onSuccess(com.payment.sdk.PaymentResponse);
    public void onFailure(com.payment.sdk.PaymentError);
    public void onCancel();
}

# 若回调类有子类,一并保留继承链
-keep class * extends com.payment.sdk.PaymentCallback {
    public void on*(...);
}
常见陷阱:回调方法的参数类型如果也是自定义实体类,必须在实体类上同时添加 keep 规则。否则虽然回调方法名保留了,但参数类型被混淆,仍然会导致参数解析失败。回调链路上的每一环都需要显式保护。

3.4 实体类保护规则

支付实体类承载着订单金额、支付状态、签名信息等核心数据,在序列化(JSON / Parcelable / Serializable)过程中字段名必须保持不变。混淆如果重命名字段,会导致 JSON 键名与后端不匹配、Parcelable 写入顺序错乱、Serializable 反序列化抛出 InvalidClassException。

# ============================================
# 实体类 keep 规则
# 保护所有字段、getter/setter、序列化相关成员
# ============================================
-keep class com.payment.sdk.entity.** {
    *;
}

# 若使用 Serializable,保留 serialVersionUID
-keepclassmembers class com.payment.sdk.entity.** implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    !static !transient <fields>;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

# 若使用 Parcelable,保留 CREATOR 字段
-keepclassmembers class com.payment.sdk.entity.** implements android.os.Parcelable {
    public static final android.os.Parcelable$Creator CREATOR;
}

3.5 综合Keep规则配置与对比

将三类keep规则合并到同一个 proguard-rules.pro 文件中,注意规则的顺序不会影响生效结果,但建议按接口 → 回调 → 实体的顺序组织,便于后期维护。下表对比三者的保护重点和指令差异:

类类型 保护目标 核心Keep指令 链路风险
支付接口 方法签名 + 参数类型 + 默认方法 -keep interface ... { public *; } 调用方反射失败,方法找不到
回调类 回调方法名 + 参数类型 + 继承链 -keep class ... { public void on*(...); } 支付结果无法投递,订单状态丢失
实体类 字段名 + 序列化ID +
Parcelable CREATOR
-keep class ... { *; } + 序列化专属规则 JSON解析异常,反序列化崩溃
# ============================================
# proguard-rules.pro 综合配置(支付核心部分)
# ============================================

# ----- 支付接口 -----
-keep interface com.payment.sdk.IPaymentChannel {
    public *;
}

# ----- 回调类 -----
-keep class com.payment.sdk.PaymentCallback {
    public void onSuccess(com.payment.sdk.PaymentResponse);
    public void onFailure(com.payment.sdk.PaymentError);
    public void onCancel();
}

# ----- 实体类 -----
-keep class com.payment.sdk.entity.** {
    *;
}
-keepclassmembers class com.payment.sdk.entity.** implements java.io.Serializable {
    static final long serialVersionUID;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
}
-keepclassmembers class com.payment.sdk.entity.** implements android.os.Parcelable {
    public static final android.os.Parcelable$Creator CREATOR;
}

3.6 调试与验证方法

配置完成后,通过以下步骤验证keep规则是否生效:

  1. 构建Release包:执行 ./gradlew assembleRelease,确保ProGuard/R8已运行。
  2. 查看映射文件:打开 build/outputs/mapping/release/mapping.txt,搜索支付核心类,确认类名、方法名、字段名是否被重命名。
  3. 反编译验证:使用 javap 或 dexdump 检查最终APK中的类结构。
# 步骤1:搜索 mapping.txt 中支付核心类的映射
grep "com.payment.sdk" build/outputs/mapping/release/mapping.txt

# 步骤2:如果 mapping.txt 中该行保持"原名称 -> 原名称",则表示 keep 成功
# 示例输出(成功):
# com.payment.sdk.IPaymentChannel -> com.payment.sdk.IPaymentChannel
# com.payment.sdk.PaymentCallback -> com.payment.sdk.PaymentCallback

# 步骤3:反编译 APK 中的 classes.jar 确认
javap -classpath build/intermediates/classes/release com.payment.sdk.IPaymentChannel

# 步骤4:检查实体类字段是否保留
javap -p -classpath build/intermediates/classes/release com.payment.sdk.entity.PaymentRequest

3.7 常见错误与优化

错误1:通配符过于宽泛导致过度保留
使用 -keep class com.payment.sdk.** { *; } 虽然能保护所有类,但会阻止混淆对非核心类的优化,增大APK体积。应精确限定到接口、回调、实体三个维度。

错误2:遗漏回调参数类型的keep规则
回调方法签名中的参数类型(如 PaymentResponse)未配置keep,导致回调参数解析失败。解决方案:对回调中出现的所有自定义类型一并添加keep规则。
优化建议:
• 将支付核心类统一放在 com.payment.sdk.core 下,使用包级keep规则减少重复配置。
• 对纯数据实体使用 -keepclassmembers 而非 -keep,避免保留无用的构造方法和内部类。
• 在CI流水线中添加自动化检查步骤:构建后自动解析mapping.txt,若支付核心类出现重命名则中止构建并告警。
核心原则总结:支付接口keep方法签名,回调类keep方法名+参数,实体类keep字段+序列化。三者缺一不可,构成支付核心代码的保护三角。