Android Alipay SDK 超频调用 SD 卡目录如何规避?
结论
应用层无法直接阻止 com.alipay.sdk:alipaysdk-android:15.8.11 在内部调用 Environment.getExternalStorageDirectory()。如果完整调用栈表明 54 次调用都来自支付宝 SDK,而业务代码只发起了一次支付,解决办法是升级到支付宝官方已经修复该问题的 SDK 版本,或联系支付宝技术支持获取修复包。
具体修复版本尚未确认时,可以先做以下处理:
- 排查是否重复创建支付任务或重复调用
PayTask.pay()。 - 移除不必要的外部存储权限,减少实际访问风险。
- 根据完整调用栈确认调用来源,避免把应用自身或其他依赖的调用算到支付宝 SDK 上。
- 将检测报告、调用栈和复现条件提交给支付宝开放平台。
- 如果合规验收要求该 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...
建议分别测试这些场景:
- 冷启动后不进行支付;
- 进入支付页面,但不调起支付宝;
- 只调用一次
PayTask.pay(); - 分别测试支付成功、取消和失败;
- 分别测试已安装和未安装支付宝客户端的情况。
如果调用发生在支付宝客户端进程,而不是当前应用进程,也要在报告中明确区分。部分检测工具会同时展示调起外部应用后的行为,不能只根据调用发生的时间判断责任组件。
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、浏览器或其他官方支付接入方式。切换支付方式通常还涉及服务端下单接口、回跳地址和风控要求,不能只替换客户端调用。
备注:内容仅供参考。
