PAYATHON 2026

Android Billing 1.1 在 SHIELD 上出现 NoSuchMethodError

数据小许

结论

问题的原因是代码压缩工具重命名了 IInAppBillingService 的方法,而 SHIELD 系统框架中存在同名类,两者因此发生冲突。

异常中的方法名为:

a(Landroid/os/IBinder;)Lcom/android/vending/billing/IInAppBillingService;

正常情况下,该方法应为:

IInAppBillingService.Stub.asInterface(IBinder)

需要在 ProGuard/R8 配置中保留 com.android.vending.billing 下相关类和方法的名称,再重新生成完整安装包:

-keep class com.android.vending.billing.** { *; }

如果条件允许,还应升级依赖旧版 Billing Library 的第三方库,停止使用 com.android.billingclient:billing:1.1。

原因

Billing Library 1.1 使用以下接口:

com.android.vending.billing.IInAppBillingService

部分 SHIELD 固件的 /system/framework/framework.jar 中也包含这个类。Android 通常优先加载系统框架中的类,因此运行时加载的可能不是 APK 中经过压缩处理的版本,而是设备固件提供的版本。

构建 release 包时,ProGuard 或 R8 可能会把 APK 中的 asInterface() 重命名为 a(),并同步修改调用位置:

IInAppBillingService.Stub.a(service)

SHIELD 系统框架中的 IInAppBillingService.Stub 没有经过应用自身的混淆,其中仍然只有 asInterface()。运行时找不到静态方法 a(IBinder),便会抛出:

java.lang.NoSuchMethodError

异常还明确指出,类声明来自:

/system/framework/framework.jar:classes2.dex

这是判断系统同名类覆盖问题的重要线索。

解决步骤

1. 添加 ProGuard/R8 保留规则

在应用模块实际使用的混淆配置文件中加入:

-keep class com.android.vending.billing.** { *; }

例如,在 app/proguard-rules.pro 中添加:

# SHIELD 固件中可能存在同名 IInAppBillingService。
# 保留类名和成员名,避免 asInterface() 被重命名。
-keep class com.android.vending.billing.** { *; }

同时确认 release 构建确实引用了该文件:

android {
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile(
                'proguard-android-optimize.txt'
            ), 'proguard-rules.pro'
        }
    }
}

使用 Kotlin DSL 时可以写成:

android {
    buildTypes {
        release {
            isMinifyEnabled = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

如果 Billing 依赖封装在自行维护的 Android Library 中,也可以将规则放入该库的 consumer rules:

android {
    defaultConfig {
        consumerProguardFiles 'consumer-rules.pro'
    }
}

在 consumer-rules.pro 中加入:

-keep class com.android.vending.billing.** { *; }

2. 清理并重新构建安装包

保留规则只会对重新压缩、混淆的产物生效。添加规则后,需要重新生成 release 包,并在设备上安装新产物。

可以检查 mapping.txt,确认以下方法没有再次被重命名为 a:

com.android.vending.billing.IInAppBillingService$Stub.asInterface

3. 升级 Billing 依赖

com.android.billingclient:billing:1.1 已经非常陈旧。应优先升级直接依赖它的第三方库,或者在兼容性允许的情况下,统一项目中的 Billing Library 版本。

升级前需要核对:

  • 第三方库是否硬编码了 Billing 1.1 的 API。
  • 项目中是否同时引入了多个 Billing Library 版本。
  • 新版本的购买确认、恢复购买和订阅处理流程是否需要调整。
  • 目标商店和 SHIELD 固件是否支持所选版本。

不能仅通过 Gradle 强制覆盖版本,就认定第三方库与新版本兼容。如果第三方库调用了旧版专有 API,强制升级可能引发新的二进制兼容问题。

为什么不应捕获这个异常

在 onServiceConnected() 外层捕获 NoSuchMethodError,只能避免进程立即崩溃,却无法获得有效的 IInAppBillingService:

mService = IInAppBillingService.Stub.asInterface(service);

这一步失败后,后续调用也无法正常执行:

mService.isBillingSupported(...)

因此,需要解决方法名不一致的问题,而不是忽略异常。

注意事项

  • 该问题通常只会出现在开启 minifyEnabled 的构建中。debug 包运行正常,并不能证明 release 包没有问题。
  • -dontwarn 只能隐藏构建警告,不能阻止方法重命名,也无法解决 NoSuchMethodError。
  • 不建议通过关闭整个应用的代码压缩来规避问题。针对 com.android.vending.billing.** 添加保留规则即可。
  • 如果添加规则后,异常中的缺失方法仍然是 a(...),说明当前构建类型没有加载该规则,或者崩溃设备上安装的仍是旧包。
  • 如果异常变成了其他 Billing API 或 Binder 错误,则需要重新分析。该保留规则只解决系统同名类与混淆方法名不一致的问题。

备注:内容仅供参考。