PAYATHON 2026

Android Alipay SDK 超频调用 SD 卡目录如何规避?

支付阿杰

结论

应用层无法直接阻止 com.alipay.sdk:alipaysdk-android:15.8.11 在内部调用 Environment.getExternalStorageDirectory()。如果完整调用栈表明 54 次调用都来自支付宝 SDK,而业务代码只发起了一次支付,解决办法是升级到支付宝官方已经修复该问题的 SDK 版本,或联系支付宝技术支持获取修复包。

具体修复版本尚未确认时,可以先做以下处理:

  1. 排查是否重复创建支付任务或重复调用 PayTask.pay()。
  2. 移除不必要的外部存储权限,减少实际访问风险。
  3. 根据完整调用栈确认调用来源,避免把应用自身或其他依赖的调用算到支付宝 SDK 上。
  4. 将检测报告、调用栈和复现条件提交给支付宝开放平台。
  5. 如果合规验收要求该 API “完全不调用”,只能暂缓使用当前版本,或评估支付宝官方支持的 H5、浏览器等替代支付方案。

仅删除存储权限不会阻止 getExternalStorageDirectory() 执行。如果合规工具统计的是 API 调用次数,删除权限也未必能通过检测。

原因分析

Environment.getExternalStorageDirectory() 是静态系统 API。第三方 AAR 编译完成后,宿主应用无法控制其内部调用频率,也不能通过缓存目录、重写 Application 或设置混淆规则来替换 SDK 的内部实现。

调用该 API 与读取 SD 卡中的用户文件不是一回事:

  • getExternalStorageDirectory() 通常只是返回外部存储根目录对应的 File。
  • 真正读写文件时,一般还会出现 FileInputStream、FileOutputStream、目录遍历等后续操作。
  • 即使应用没有申请存储权限,该 API 本身仍可能被调用。
  • 合规检测工具可能只统计 API 的调用频率,所以即使没有读取文件,检测结果也不一定合格。

这 54 次调用是否全部由 SDK 15.8.11 产生,需要根据运行时调用栈判断,不能只看依赖列表。

处理步骤

1. 确认应用实际使用的 SDK 版本

先查看 Gradle 的最终解析结果,确认声明的版本没有被其他依赖覆盖:

./gradlew :app:dependencyInsight \
  --dependency alipaysdk-android \
  --configuration releaseRuntimeClasspath

还要检查 APK 或 AAB 中是否包含多个支付宝 SDK,以及项目是否同时集成了手动下载的 AAR 和 Maven 依赖。重复集成可能造成不可预期的行为。

2. 检查支付请求是否被重复触发

连续点击、生命周期重入、事件重复消费或网络重试,都可能导致应用多次创建 PayTask。可以在业务入口加一层互斥保护:

private val paying = java.util.concurrent.atomic.AtomicBoolean(false)

fun startAlipay(activity: Activity, orderInfo: String) {
    if (!paying.compareAndSet(false, true)) {
        return
    }

    Thread {
        try {
            val result = com.alipay.sdk.app.PayTask(activity).pay(orderInfo, true)
            // 将支付结果切换到主线程处理
        } finally {
            paying.set(false)
        }
    }.start()
}

这段代码只能防止业务层重复发起支付。如果一次 pay() 调用就会在 SDK 内部产生数十次目录查询,它不能减少这些内部调用。

还需要检查:

  • 支付按钮是否注册了多个监听器;
  • LiveData、Flow 或事件总线是否重复消费支付事件;
  • 支付失败后的重试逻辑是否再次调用了 pay();
  • 页面恢复时是否在 onResume() 中重新发起支付;
  • 同一笔订单是否创建了多个 PayTask。

3. 获取完整调用栈

使用合规检测工具时,应保留每次调用的完整堆栈,重点检查其中是否包含类似路径:

android.os.Environment.getExternalStorageDirectory
...
com.alipay.sdk...

建议分别测试这些场景:

  1. 冷启动后不进行支付;
  2. 进入支付页面,但不调起支付宝;
  3. 只调用一次 PayTask.pay();
  4. 分别测试支付成功、取消和失败;
  5. 分别测试已安装和未安装支付宝客户端的情况。

如果调用发生在支付宝客户端进程,而不是当前应用进程,也要在报告中明确区分。部分检测工具会同时展示调起外部应用后的行为,不能只根据调用发生的时间判断责任组件。

4. 移除非必要的存储权限

如果应用本身不需要访问共享存储,可以在主工程的清单中移除依赖合并进来的权限:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission
        android:name="android.permission.READ_EXTERNAL_STORAGE"
        tools:node="remove" />

    <uses-permission
        android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        tools:node="remove" />

</manifest>

修改后,查看 Android Studio 中的 Merged Manifest,确认这些权限没有出现在最终产物中。随后完整回归以下流程:

  • 已安装支付宝客户端时的支付流程;
  • 未安装支付宝客户端时的降级流程;
  • 支付成功、取消和异常返回;
  • 不同 Android 版本下的支付结果回调。

不要为了消除告警而拦截或伪造系统目录。移除权限只能降低权限层面的风险,无法改变 SDK 内部调用该 API 的次数。

5. 升级 SDK,但不要猜测修复版本

查询支付宝开放平台当前的发布说明,或向支付宝技术支持确认:

  • 哪个 Android SDK 版本修复了重复调用问题;
  • 修复范围是否包括 Environment.getExternalStorageDirectory();
  • 是否需要额外配置隐私合规接口;
  • 新版本支持的最低 Android 版本,以及升级时有哪些注意事项。

如果没有官方发布说明或实际复测结果,不要直接认定某个版本已经解决问题。升级后仍要使用相同设备、相同支付路径和相同检测窗口重新测试。

Gradle 配置可以采用集中管理,实际版本号填写支付宝官方确认的值:

dependencies {
    implementation("com.alipay.sdk:alipaysdk-android:${alipaySdkVersion}@aar")
}

升级前还要检查混淆规则、回调解析、支付结果码处理和包体变化。

不建议采用的方式

不建议通过 Hook、字节码替换、反射修改 SDK,也不要在生产环境中拦截 Environment,原因包括:

  • 静态系统方法不能通过普通继承重写;
  • Hook 方案依赖具体的系统版本或运行环境,稳定性较差;
  • 修改闭源 SDK 可能影响签名校验和支付流程;
  • 这种做法可能违反 SDK 使用约定,并带来新的安全风险。

ProGuard 或 R8 也不会自动删除仍会执行的 SDK 调用。

无法立即升级时的选择

如果必须继续使用当前版本,可以先收集完整调用栈,做好支付入口防重和权限收敛,并向合规审核方解释 API 调用与实际文件访问之间的区别。最终能否通过审核仍取决于具体规则,不能保证提交说明后就一定可以通过。

如果审核规则明确要求该 API 在指定时间内不得高频调用,而支付宝暂时没有可用的修复版本,就应停止发布包含该 SDK 版本的应用。也可以在确认业务和支付宝侧都支持的情况下,评估 H5、浏览器或其他官方支付接入方式。切换支付方式通常还涉及服务端下单接口、回跳地址和风控要求,不能只替换客户端调用。

备注:内容仅供参考。