Flutter开发中如何实现Host Card Emulation (HCE)功能?
Flutter HCE功能落地完整实现方案
你当前使用的nfc_manager包仅支持NFC标签读写、读卡器模式相关能力,不提供HCE(主机卡模拟)的开箱支持。HCE不存在纯Dart层的实现方案,必须通过MethodChannel桥接各平台原生HCE能力落地,以下是可直接跑通的实现流程,覆盖安卓、iOS双端。
前置平台限制说明
- 安卓端:Android 4.4及以上版本原生支持HCE,无需Root权限,但需要声明对应NFC权限、配置AID(应用标识符)过滤规则,仅当外部读卡器选中匹配的AID时,才会唤起应用的HCE服务。
- iOS端:限制极强,iOS 13+才开放HCE能力,仅支持模拟ISO7816-4规范的接触式智能卡,不支持Mifare Classic等常见门禁/公交卡协议模拟;必须使用付费苹果开发者账号申请HCE专属权限,配置对应entitlements文件才能调用,普通免费开发者账号无法使用;且模拟的卡片仅能被安卓设备、专用POS等外部读卡器识别,无法被另一台iOS设备读取,App退到后台后HCE会话会自动断开。
分步实现流程
1. Flutter层公共桥接逻辑
HCE的核心APDU(应用协议数据单元)收发逻辑在原生层实现,Flutter层通过MethodChannel实现状态监听、服务启停、数据交互,核心代码如下:
import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; class HceController { static const MethodChannel _hceChannel = MethodChannel('com.yourpackage.hce'); /// 监听HCE收到的读卡器APDU指令、服务运行状态 static final ValueNotifier<Map<String, dynamic>> hceEvent = ValueNotifier({}); static final ValueNotifier<bool> isHceRunning = ValueNotifier(false); static void initHceBridge() { _hceChannel.setMethodCallHandler((call) async { switch (call.method) { case "onApduReceived": hceEvent.value = Map<String, dynamic>.from(call.arguments); break; case "onHceStateChanged": isHceRunning.value = call.arguments as bool; break; } }); } /// 启动HCE服务,传入自定义AID、要模拟的卡片数据 static Future<bool> startHce({required String aid, required List<int> mockCardData}) async { return await _hceChannel.invokeMethod('startHce', { "aid": aid, "cardData": mockCardData, }); } /// 停止HCE服务 static Future<void> stopHce() async { await _hceChannel.invokeMethod('stopHce'); } /// 向读卡器返回APDU响应数据 static Future<void> sendApduResponse(List<int> responseBytes) async { await _hceChannel.invokeMethod('sendApduResponse', { "response": responseBytes, }); } }
在App启动入口处调用HceController.initHceBridge()完成桥接初始化即可。
2. 安卓端原生适配
2.1 清单文件配置
在android/app/src/main/AndroidManifest.xml中添加权限、注册HCE服务:
<uses-permission android:name="android.permission.NFC" /> <uses-feature android:name="android.hardware.nfc.hce" android:required="true" /> <application> <!-- 其他配置省略 --> <service android:name=".HceServiceImpl" android:exported="true" android:permission="android.permission.BIND_NFC_SERVICE"> <intent-filter> <action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE" /> </intent-filter> <meta-data android:name="android.nfc.cardemulation.host_apdu_service" android:resource="@xml/hce_aid_config" /> </service> </application>
在android/app/src/main/res/xml目录下新建hce_aid_config.xml,配置AID过滤规则:
<?xml version="1.0" encoding="utf-8"?> <host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android" android:description="HCE服务" android:requireDeviceUnlock="false"> <aid-group android:category="other" android:description="自定义卡模拟"> <!-- 替换为你自己的AID,自定义AID建议以F开头,属于国际未分配段避免冲突 --> <aid-filter android:name="F0010203040506" /> </aid-group> </host-apdu-service>
2.2 HCE服务实现
新建HceServiceImpl.kt,继承系统HostApduService实现APDU收发,和Flutter层通信:
package com.yourpackage // 替换为你自己的包名 import android.nfc.cardemulation.HostApduService import android.os.Bundle import io.flutter.embedding.engine.FlutterEngineCache import io.flutter.plugin.common.MethodChannel class HceServiceImpl : HostApduService() { private lateinit var channel: MethodChannel private val SELECT_APDU_PREFIX = byteArrayOf(0x00, 0xA4.toByte(), 0x04, 0x00) private val RESP_SUCCESS = byteArrayOf(0x90.toByte(), 0x00) private val RESP_FAIL = byteArrayOf(0x6A.toByte(), 0x82.toByte()) private var targetAid: ByteArray? = null private var mockCardData: ByteArray? = null override fun onCreate() { super.onCreate() // 从缓存获取Flutter引擎实例,建立通信通道 val engine = FlutterEngineCache.getInstance().get("hce_flutter_engine") channel = MethodChannel(engine!!.dartExecutor.binaryMessenger, "com.yourpackage.hce") channel.setMethodCallHandler { call, result -> when(call.method) { "startHce" -> { targetAid = (call.argument<String>("aid")!!).hexToBytes() mockCardData = (call.argument<List<Int>>("cardData")!!).map { it.toByte() }.toByteArray() result.success(true) } "stopHce" -> { targetAid = null mockCardData = null result.success(null) } "sendApduResponse" -> { val resp = (call.argument<List<Int>>("response")!!).map { it.toByte() }.toByteArray() sendResponseApdu(resp) result.success(null) } } } channel.invokeMethod("onHceStateChanged", true) } override fun processCommandApdu(command: ByteArray?, p1: Bundle?): ByteArray { command ?: return RESP_FAIL // 将收到的APDU指令透传给Flutter层 channel.invokeMethod("onApduReceived", mapOf("apdu" to command.map { it.toInt() and 0xFF })) // 处理AID选择指令 if (command.size >= SELECT_APDU_PREFIX.size) { val prefix = command.copyOfRange(0, SELECT_APDU_PREFIX.size) if (SELECT_APDU_PREFIX.contentEquals(prefix)) { val aidLen = command[4].toInt() val reqAid = command.copyOfRange(5, 5 + aidLen) return if (reqAid.contentEquals(targetAid)) RESP_SUCCESS else RESP_FAIL } } // 其他指令默认返回模拟卡数据+成功状态,可根据业务逻辑自定义返回 return mockCardData?.plus(RESP_SUCCESS) ?: RESP_FAIL } override fun onDeactivated(reason: Int) { channel.invokeMethod("onHceStateChanged", false) } override fun onDestroy() { super.onDestroy() channel.invokeMethod("onHceStateChanged", false) } // 十六进制字符串转字节数组工具方法 private fun String.hexToBytes(): ByteArray { require(length % 2 == 0) return chunked(2).map { it.toInt(16).toByte() }.toByteArray() } }
2.3 引擎缓存配置
在MainActivity.kt中提前缓存Flutter引擎,保证HCE服务能正常和Flutter层通信:
import android.os.Bundle import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.embedding.engine.FlutterEngineCache class MainActivity : FlutterActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val engine = FlutterEngine(this) engine.dartExecutor.executeDartEntrypoint( io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint.createDefault() ) FlutterEngineCache.getInstance().put("hce_flutter_engine", engine) } }
3. iOS端原生适配
- 权限配置:在苹果开发者后台给App ID开启Host Card Emulation权限,生成匹配的描述文件,在Xcode中打开项目的entitlements文件添加如下配置:
在Info.plist中添加NFC权限说明:<key>com.apple.developer.nfc.hce</key> <true/> <key>com.apple.developer.nfc.hce.iso7816.select-identifier-prefixes</key> <array> <string>F00102030405</string> <!-- 替换为你自己的AID前缀 --> </array><key>NFCReaderUsageDescription</key> <string>需要使用NFC实现卡模拟功能</string> - 核心逻辑实现:导入CoreNFC框架,创建
NFCHostCardEmulationSession实例实现APDU收发,逻辑和安卓端对齐:接收Flutter层的启动参数、将收到的APDU指令透传给Flutter、调用系统API返回APDU响应、同步HCE会话状态即可。注意iOS的HCE会话必须在App前台启动,退后台自动断开。
常见踩坑点
- 不要尝试找纯Dart的HCE插件,目前所有公开Flutter NFC插件都只覆盖读卡器、写卡模式,HCE必须写原生桥接
- iOS HCE能力有强权限限制,拿不到官方entitlement的情况下不用做兼容尝试,完全无法调用
- 安卓部分定制ROM会默认拦截第三方HCE服务,需要引导用户开启自启动、后台无限制权限
- 调试时必须用外部NFC设备/另一台NFC手机当读卡器,不能用同设备的NFC读取自己模拟的卡片
- 自定义AID不要使用官方分配给金融、交通等场景的段,建议以F开头避免冲突
内容的提问来源于stack exchange,提问作者Kumaresh Chandra Baruri
相关产品推荐
相关产品推荐

