Android 调用支付宝支付时 APayEntranceActivity 崩溃
结论
崩溃发生在支付宝 SDK 内部。APayEntranceActivity 执行 finish() 时使用了空的 Map key,最终调用 ConcurrentHashMap.remove(null) 并抛出异常。
ConcurrentHashMap 不允许 key 为 null。这通常不是业务代码直接调用 remove() 导致的,而是 APayEntranceActivity 启动时没有拿到 SDK 需要的标识参数。可能的原因包括:
- 业务代码直接或间接启动了
APayEntranceActivity。 - 支付流程被重复或并发调用,或者 Activity 状态异常。
- 支付宝 SDK 版本较旧,内部生命周期处理存在兼容性问题。
- 工程中存在多个支付宝 SDK,造成类或 Manifest 配置冲突。
- App 进程被系统回收后恢复了
APayEntranceActivity,但 SDK 保存在内存中的上下文已经丢失。 - 加固、热修复、代码混淆或 Manifest 修改破坏了 SDK 的参数传递。
只看这段堆栈还无法判断具体属于哪种情况,但可以确定,SDK 没有妥善处理空标识。
不要直接启动 APayEntranceActivity
APayEntranceActivity 是支付宝 SDK 内部使用的跳转页,业务代码不应通过 Intent 启动它。例如,不要这样写:
Intent intent = new Intent();
intent.setClassName(
getPackageName(),
"com.alipay.sdk.app.APayEntranceActivity"
);
startActivity(intent);
应通过 SDK 提供的 PayTask 发起支付,由 SDK 创建跳转所需的参数:
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(() -> {
PayTask payTask = new PayTask(activity);
Map<String, String> result = payTask.payV2(orderInfo, true);
activity.runOnUiThread(() -> {
String resultStatus = result.get("resultStatus");
String memo = result.get("memo");
// 更新页面状态。
// 最终支付结果应以服务端异步通知或服务端主动查询为准。
});
});
orderInfo 应由服务端生成并签名。不要把商户私钥保存在客户端,也不要在客户端拼接包含私钥签名逻辑的订单。
建议的排查步骤
1. 检查 SDK 接入方式
确认项目只使用一种方式引入支付宝 SDK,不要同时保留 Maven 依赖、本地 AAR 和旧版 JAR。
重点检查:
app/libs中是否残留旧版支付宝 JAR 或 AAR。- Gradle 依赖是否引入了多个不同版本。
- 其他支付组件是否传递依赖了另一个支付宝 SDK。
- 最终 APK 中是否包含重复的
com.alipay.sdk类。
不要只替换新的 AAR,却继续保留旧 JAR。这样很容易造成代码版本与资源、Manifest 不一致。
2. 升级到当前官方推荐版本
如果项目使用的是旧版 SDK,应优先按照支付宝开放平台当前的接入文档升级。异常发生在 SDK 内部。如果问题来自某个版本的生命周期缺陷,业务层通常很难可靠规避。
升级时一并检查:
- Gradle 依赖配置。
- Manifest 合并结果。
- 混淆配置。
- ABI 和打包配置。
- 旧 SDK 文件是否已彻底移除。
不能仅凭 APayEntranceActivity.java:59 判断 SDK 的具体版本,因为不同构建版本的代码行号可能不同。
3. 检查最终合并后的 Manifest
不要只检查源码中的 AndroidManifest.xml,还要查看构建生成的 merged manifest,确认 APayEntranceActivity 没有被重复声明,也没有被第三方配置覆盖。
尤其要检查是否通过 tools:replace 修改了 SDK Activity 的以下属性:
android:launchModeandroid:taskAffinityandroid:processandroid:exportedandroid:theme
除非官方接入文档明确要求,否则不要手动修改内部 Activity 的启动模式、进程或任务栈属性。通常应让 SDK 自带的 Manifest 配置随依赖正常合并。
4. 防止重复发起支付
连续快速点击支付按钮,或者多个回调同时发起支付,都可能打乱 SDK 内部保存的请求与回调对应关系。可以在业务层增加单次支付锁:
private final AtomicBoolean paying = new AtomicBoolean(false);
public void startAlipay(Activity activity, String orderInfo) {
if (activity == null || activity.isFinishing()) {
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1
&& activity.isDestroyed()) {
return;
}
if (orderInfo == null || orderInfo.trim().isEmpty()) {
return;
}
if (!paying.compareAndSet(false, true)) {
return;
}
executor.execute(() -> {
try {
PayTask payTask = new PayTask(activity);
Map<String, String> result = payTask.payV2(orderInfo, true);
activity.runOnUiThread(() -> handlePayResult(result));
} catch (Throwable throwable) {
activity.runOnUiThread(() -> handlePayError(throwable));
} finally {
paying.set(false);
}
});
}
这里的 try/catch 只能捕获调用线程中的异常。题目中的异常出现在另一个 Activity 的主线程启动阶段,因此,在 payV2() 外层捕获异常并不能彻底阻止这次崩溃。解决问题仍需修正接入方式或升级 SDK。
5. 检查 Activity 和进程生命周期
记录发起支付前后的这些信息:
Log.d("AlipayDebug", "activity=" + activity);
Log.d("AlipayDebug", "finishing=" + activity.isFinishing());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
Log.d("AlipayDebug", "destroyed=" + activity.isDestroyed());
}
Log.d("AlipayDebug", "taskId=" + activity.getTaskId());
Log.d("AlipayDebug", "pid=" + Process.myPid());
如果崩溃主要发生在以下场景,应重点检查进程恢复或任务栈问题:
- 在支付宝停留较长时间后返回。
- App 进入后台后被系统回收。
- 开发者选项启用了“不保留活动”。
- App 使用多进程。
- 从支付页面返回时发生横竖屏切换。
- 支付期间宿主 Activity 被销毁,或者任务栈被清理。
可以在开发者选项中开启“不保留活动”,再通过限制后台、切换应用等方式尝试复现。如果问题只在进程重建后出现,应把 SDK 版本、系统版本、支付宝客户端版本和完整日志一并提交给支付宝技术支持。
6. 排除混淆、加固和热修复的影响
分别测试 Debug 包、未混淆的 Release 包和正式加固包:
- 只有混淆后崩溃:检查是否遗漏或覆盖了 SDK 随包提供的混淆规则。
- 只有加固后崩溃:检查加固工具是否修改了 Activity、Intent extras 或类加载过程。
- 只有热修复版本崩溃:检查新旧 APK 中的支付宝 SDK 是否发生了不完整替换。
- 所有构建都崩溃:更可能与调用方式、依赖冲突或 SDK 本身有关。
混淆规则应以当前 SDK 随附的说明为准,不要直接照搬来源不明的旧版规则。
为什么业务层无法直接修复 finish()
堆栈中的关键调用是:
APayEntranceActivity.finish()
ConcurrentHashMap.remove()
NullPointerException
这说明 SDK 在 finish() 中执行了类似下面的代码:
map.remove(requestKey);
此时 requestKey 是 null。即使 Map 中不存在对应记录,remove(nonNullKey) 也不会抛出这个异常。问题在于传入的 key 本身为空。
这个字段和 Map 都属于 SDK 的内部实现,不建议通过反射、继承或替换同名类来修补,否则可能造成签名跳转失败、回调丢失或后续升级冲突。
提交问题时应保留的信息
清理依赖并升级 SDK 后,如果问题仍能复现,应收集以下信息:
- 支付宝 SDK 的准确版本和引入方式。
- 手机品牌、Android 版本和 ROM 版本。
- 支付宝客户端版本。
- 是否使用加固、热修复或插件化框架。
- 是否启用多进程。
- 从发起支付到崩溃之间的完整 Logcat。
- merged manifest 中
APayEntranceActivity的最终配置。 - 问题是否只在进程回收、重复点击或取消支付后出现。
日志中不要包含完整订单字符串、签名、用户标识或其他敏感数据。
注意事项
- 支付结果不能只看客户端返回值,应以服务端异步通知或主动查询结果为准。
- 不要在捕获该异常后直接判定支付失败,因为支付可能已经在支付宝侧完成。
- 不要自行注册、启动或覆写
APayEntranceActivity。 - Android 12 的
android:exported配置问题通常表现为安装失败或 Activity 启动异常,无法单独解释这里的ConcurrentHashMap.remove(null)。 - 如果崩溃集中出现在某个 SDK 版本或特定 ROM 上,应先用最小工程验证。若最小工程运行正常,再逐项恢复加固、混淆、热修复和支付封装代码,以便定位冲突来源。
备注:内容仅供参考。
