Android 4.4 调用 CardEmulation.getInstance() 异常
结论
CardEmulation 出现在 API 19 中,只能说明 Android 4.4 提供了这套 API,并不意味着所有搭载 Android 4.4 的 NFC 设备都支持主机卡模拟(HCE)。
NFC 与 HCE 是两项不同的能力。调用 CardEmulation.getInstance() 前,需要检查:
- 系统版本是否为 API 19 及以上;
- 设备是否声明
PackageManager.FEATURE_NFC_HOST_CARD_EMULATION; NfcAdapter是否存在;- 如果需要立即使用 NFC,还应检查 NFC 当前是否已开启。
设备不支持 HCE 时,调用 CardEmulation.getInstance() 可能直接抛出 UnsupportedOperationException。
为什么有 NFC 仍然不支持 Card Emulation
NFC 硬件可能只支持标签读写、P2P 等功能,并不一定支持 HCE。HCE 能否使用,还会受到 NFC 控制器、驱动、系统固件和厂商系统配置的影响。
Android 4.4 虽然引入了 HCE API,但厂商仍可生产带有 NFC 芯片、运行 Android 4.4,却没有声明或实现 HCE 的设备。这类设备通常不会在设置中显示 “Tap & pay” 或类似入口。
所以,下面两组条件并不等价:
设备有 NFC ≠ 设备支持 HCE
Android 版本 >= 4.4 ≠ 设备支持 CardEmulation
异常堆栈中的进程是 com.android.settings,这说明系统的支付设置页面也没有正确处理设备不支持 HCE 的情况。问题出在设备的系统实现,但应用仍需自行检测能力,不能根据设置页面是否存在来判断。
正确的检测方式
可以先封装一个 HCE 能力检查方法:
private boolean supportsHostCardEmulation(Context context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
return false;
}
PackageManager packageManager = context.getPackageManager();
if (!packageManager.hasSystemFeature(PackageManager.FEATURE_NFC)) {
return false;
}
if (!packageManager.hasSystemFeature(
PackageManager.FEATURE_NFC_HOST_CARD_EMULATION)) {
return false;
}
return NfcAdapter.getDefaultAdapter(context) != null;
}
确认设备支持 HCE 后,再调用 CardEmulation.getInstance():
private boolean isDefaultPaymentService(Context context) {
if (!supportsHostCardEmulation(context)) {
return false;
}
NfcAdapter adapter = NfcAdapter.getDefaultAdapter(context);
if (adapter == null) {
return false;
}
try {
CardEmulation cardEmulation = CardEmulation.getInstance(adapter);
ComponentName service = new ComponentName(
context,
MyPaymentService.class
);
return cardEmulation.isDefaultServiceForCategory(
service,
CardEmulation.CATEGORY_PAYMENT
);
} catch (UnsupportedOperationException e) {
// 防御厂商 ROM 的错误声明或不完整实现
return false;
}
}
主要判断依据是 hasSystemFeature()。同时保留对 UnsupportedOperationException 的捕获,可以兼容少数错误声明了 HCE 特性,但实际实现不完整的厂商 ROM。
区分“支持”“开启”和“默认服务”
这几个状态需要分开判断。
检查设备是否支持 HCE:
getPackageManager().hasSystemFeature(
PackageManager.FEATURE_NFC_HOST_CARD_EMULATION
);
检查设备是否具有 NFC 适配器:
NfcAdapter adapter = NfcAdapter.getDefaultAdapter(context);
boolean hasAdapter = adapter != null;
检查 NFC 当前是否开启:
boolean nfcEnabled = adapter != null && adapter.isEnabled();
检查应用是否为默认支付服务:
boolean isDefault =
CardEmulation.getInstance(adapter)
.isDefaultServiceForCategory(
new ComponentName(context, MyPaymentService.class),
CardEmulation.CATEGORY_PAYMENT
);
isDefaultServiceForCategory() 不能用来通用检测 card emulation 是否已启用。它只负责检查指定服务当前是否为某个类别的默认服务。返回 false 可能有多种原因:
- 设备支持 HCE,但当前 NFC 已关闭;
- 应用不是默认支付服务;
- 用户尚未选择默认支付应用;
- 服务配置不正确;
- 厂商系统对支付类别有额外限制。
因此,不能只通过这个方法判断设备是否支持 HCE。
Manifest 配置
如果应用允许安装在不支持 HCE 的设备上,应把相关硬件声明为可选:
<uses-feature
android:name="android.hardware.nfc"
android:required="false" />
<uses-feature
android:name="android.hardware.nfc.hce"
android:required="false" />
如果 HCE 是应用的核心功能,缺少 HCE 时应用无法使用,可以将其声明为必需:
<uses-feature
android:name="android.hardware.nfc.hce"
android:required="true" />
应用商店会据此过滤不支持 HCE 的设备。运行时仍建议保留能力检查,以免厂商 ROM 配置异常。
注意事项
FEATURE_NFC_HOST_CARD_EMULATION检测的是主机卡模拟能力,不表示设备支持所有依赖安全元件、SIM/UICC 或厂商私有方案的卡模拟方式。- 不要只根据 Android 版本、NFC 设置是否存在或
NfcAdapter是否非空来判断 HCE。 - 用户关闭 NFC 后,设备仍然支持 HCE,只是暂时无法使用 NFC。设备能力与开关状态应分别检测。
- 设备不支持 HCE 时,应隐藏支付设置入口或给出明确提示,不要继续打开系统支付选择页面。
- 对 Android 4.4 时代的厂商 ROM,除特性检测外,还应捕获
UnsupportedOperationException,避免系统声明与实际实现不一致导致应用崩溃。
备注:内容仅供参考。
