更新记录

1.0.0(2026-09-29)

  • 首次发布 Android 与 HarmonyOS 手机 NFC 虚拟卡功能,支持 HCE / ISO-DEP / ISO 7816-4 APDU 卡模拟。
  • 支持动态 AID、内置测试卡、自定义 APDU 请求与响应、超时保护、测试数据读写及生命周期诊断。
  • Android 使用系统 HostApduService,支持后台恢复内置测试卡;停止后占位 AID 返回 6A82,自定义请求超时返回 6400。
  • HarmonyOS 额外提供 ndefTag 模式,以标准 AID D2760000850101 模拟可重复写入的 NFC Forum Type 4 NDEF 标签。
  • Type 4 NDEF 模式支持 CC 文件、NDEF 文件、READ BINARY 与 UPDATE BINARY,可供 Android 系统 NDEF 接口读写。
  • 提供 uni-app 与 uni-app x 中文示例,包含状态诊断、运行配置、测试数据和读卡端 APDU 指令。
  • iOS、Web 和小程序暂不支持,调用时返回结构化不支持错误。
  • 本插件包含 Android 原生服务、Manifest/AID 资源和 HarmonyOS 原生 ETS;接入后必须重新制作对应平台的自定义基座、HAP 或正式安装包。

平台兼容性

uni-app(4.84)

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
√ √ × × √ × √ × √
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
- - - - - - - - - - - -

uni-app x(4.84)

Chrome Safari Android iOS 鸿蒙 微信小程序
× × √ × √ ×

lizhao-hce-card

lizhao-hce-card 是面向 Android 与 HarmonyOS 的 HCE 卡模拟 UTS 插件。它把手机模拟成自定义的 ISO 14443-4 / ISO-DEP / ISO 7816-4 APDU 卡;HarmonyOS 还可模拟可重复写入的 NFC Forum Type 4 NDEF 标签。

ndefTag 是基于 HCE 的 Type 4 软件标签,只在 HarmonyOS 提供。它可用于 Android Ndef 读写验证,但不是 MIFARE、NdefFormatable 或可永久锁定的实体标签。

能力与边界

  • Android HostApduService 与 HarmonyOS HceService 原生 HCE / ISO-DEP / ISO 7816-4 短 APDU 卡模拟。
  • 运行时动态注册 5~16 字节 AID。
  • 内置测试 Applet:SELECT、GET、PUT、ECHO、CLEAR。
  • custom 模式把 APDU 交给页面,并按 requestId 返回业务响应。
  • HarmonyOS ndefTag 模式:标准 NDEF AID、CC 文件、NDEF 文件、READ BINARY、UPDATE BINARY 与 NLEN 写事务。
  • 单待响应请求、超时 6400、过期请求拒绝和生命周期状态诊断。
  • HarmonyOS 测试数据保存在当前应用进程;Android 为承接系统后台唤起,会保存在应用私有目录,卸载应用后清除。
  • APDU 单次数据仍按短 APDU 处理,单条最多 255 字节;ndefTag 可通过多次 UPDATE BINARY 写入最长 2046 字节的 NDEF Message。
  • 不模拟 MIFARE、NdefFormatable、永久只读锁定和断电保存;NDEF 内容仅在当前 HCE 运行会话内保留。

平台支持

平台 状态 说明
HarmonyOS App 支持 testApplet、custom、ndefTag;使用系统 HceService,需要宿主 Ability 配置和权限
Android App 支持 testApplet、custom;使用系统 HostApduService 和 CardEmulation 动态 AID,最低 API 21
iOS App 不支持 返回 9071001
Web / 小程序 不支持 返回 9071001,不伪造成功

插件同时提供 uni-app 与 uni-app x 示例,公开 API 统一从插件根目录导入。

Android 宿主配置

Android 所需的 NFC 权限、HCE feature、HostApduService、service metadata 和 AID XML 已随 插件放在 utssdk/app-android,HBuilderX 原生联编时会合并进入宿主安装包。接入或从 1.0.0 升级后必须重新制作自定义基座或正式 APK;只更新 wgt/appResource 不会把 Service 和 XML 加入旧基座。

Android 使用 CardEmulation.CATEGORY_OTHER,不注册支付类 AID。XML 中只声明内部占位 AID F04C495A48434500,业务 AID 由 startHce 动态注册并在 stopHce 时注销;插件停止时即使 系统把内部占位 AID 路由到 Service,也只返回 6A82。

Android 的 android.permission.NFC 是普通权限,不会出现运行时授权框。startHce 前仍会 检查设备 HCE 能力、NFC 开关和宿主 Service 声明;缺失时分别返回稳定错误,不会伪造启动成功。

HarmonyOS 宿主配置

HCE 的 Ability action、静态默认 AID 和权限属于宿主应用配置,不能仅依靠 HAR 内部声明。请把以下内容合并到项目根目录的 harmony-configs/entry/src/main/module.json5,保留原有 home skill 和其他权限:

{
  "module": {
    "abilities": [
      {
        "name": "EntryAbility",
        "skills": [
          {
            "entities": ["entity.system.home"],
            "actions": ["action.system.home"]
          },
          {
            "actions": [
              "ohos.nfc.cardemulation.action.HOST_APDU_SERVICE"
            ]
          }
        ],
        "metadata": [
          {
            "name": "other-aid",
            "value": "F0010203040506"
          }
        ]
      }
    ],
    "requestPermissions": [
      {
        "name": "ohos.permission.NFC_CARD_EMULATION",
        "reason": "$string:card_emulation_reason"
      }
    ]
  }
}

同时在 harmony-configs/entry/src/main/resources/base/element/string.json 的 string 数组中加入:

{
  "name": "card_emulation_reason",
  "value": "用于在用户主动启动测试时模拟 ISO-DEP APDU 卡并与读卡设备通信"
}

默认 AID 使用 other-aid,不声明支付类 payment-aid。

ndefTag 启动时会把运行期 AID 固定为 NFC Forum NDEF Application AID D2760000850101,无需把它写成业务 AID。宿主仍必须保留上述 HCE action、默认 metadata 和权限, 以便系统识别当前 Ability 为 HCE 服务。

最小启动示例

import {
  startHce,
  stopHce,
  onApduCommand,
  offApduCommand
} from '@/uni_modules/lizhao-hce-card'

const handleApdu = (event) => {
  console.log('收到 APDU', event.apduHex)
}

onApduCommand(handleApdu)

startHce({
  mode: 'testApplet',
  aidList: ['F0010203040506'],
  initialDataHex: '6C697A68616F2D6863652D63617264',
  success: (res) => console.log('HCE 已启动', res),
  fail: (err) => console.error('HCE 启动失败', err),
  complete: () => console.log('启动调用结束')
})

// 示例页选择在卸载时释放动态 AID;业务若需要 testApplet 后台响应,可在合适的业务时机再停止。
stopHce({
  complete: () => offApduCommand(handleApdu)
})

进入示例页只检查状态,不会自动启动 HCE;必须由用户主动点击启动。

HarmonyOS 可写 Type 4 NDEF

import { startHce } from '@/uni_modules/lizhao-hce-card'

startHce({
  mode: 'ndefTag',
  // 原始 NDEF Message,不包含两字节 NLEN。这里是文本 lizhao-hce-card。
  initialNdefMessageHex: 'D1011254027A686C697A68616F2D6863652D63617264',
  ndefCapacityBytes: 2046,
  success: (res) => console.log('Type 4 NDEF 已启动', res),
  fail: (err) => console.error('启动失败', err),
  complete: () => {}
})

此模式固定使用以下协议对象:

对象 值 / 行为
NDEF Application AID D2760000850101
CC 文件 E103,Mapping Version 2.0,MLe/MLc 均为 255
NDEF 文件 E104,默认文件总长 2048 字节,其中 NDEF Message 容量 2046 字节
读取 READ BINARY
写入 UPDATE BINARY;支持先写 NLEN=0、分段写正文、最后提交 NLEN

Android 端通过 Ndef.writeNdefMessage() 写入后,内容会保留在当前 HCE 运行会话中;两台手机移开再重新贴合,仍可读到新内容。停止 HCE、应用进程结束或重新启动 ndefTag 后,会恢复为新一轮启动时传入的初始内容。

ndefTag 模式的 AID 由协议固定,因此不能调用 updateHceAids();该调用会返回 9071007。

内置测试 Applet

默认 AID:F0010203040506

默认数据是 UTF-8 文本 lizhao-hce-card:

6C697A68616F2D6863652D63617264

读卡端应先 SELECT:

00A4040007F0010203040506

成功响应:

6F098407F00102030405069000
操作 APDU 成功响应
SELECT 默认 AID 00A4040007F0010203040506 最小 FCI + 9000
GET 当前数据 80CA000000 数据 + 9000
PUT 010203 80DA000003010203 9000
ECHO A1B2C3 80EE000003A1B2C3 A1B2C39000
CLEAR 80E4000000 9000

常用状态字:

状态字 含义
9000 成功
6700 APDU 长度不合法
6985 尚未 SELECT 当前 Applet
6A82 AID 不存在
6D00 INS 不支持
6E00 CLA 不支持
6400 custom 模式响应超时
6F00 内部处理异常时的兜底状态字

custom 自定义响应

import {
  startHce,
  onApduCommand,
  sendApduResponse
} from '@/uni_modules/lizhao-hce-card'

onApduCommand((event) => {
  // requestId 只对当前尚未响应的命令有效。
  sendApduResponse({
    requestId: event.requestId,
    responseHex: '0102039000',
    success: (res) => console.log('响应已发送', res),
    fail: (err) => console.error('响应失败', err),
    complete: () => {}
  })
})

startHce({
  mode: 'custom',
  aidList: ['F0010203040506'],
  responseTimeoutMs: 800,
  success: () => {},
  fail: (err) => console.error(err),
  complete: () => {}
})
  • responseHex 必须是偶数长度十六进制,至少包含 SW1/SW2,最多 257 字节。
  • 新 APDU 到来时,旧 requestId 立即失效,插件不会把旧响应误发给新命令。
  • 当前请求超时后系统发送 6400;随后再用该 requestId 响应会返回 9071008。

动态更新 AID

import { updateHceAids } from '@/uni_modules/lizhao-hce-card'

updateHceAids({
  aidList: ['F0010203040506', 'A00000000101'],
  success: (res) => console.log('新 AID 已生效', res.aidList),
  fail: (err) => console.error('更新失败', err),
  complete: () => {}
})

更新顺序固定为停止/注销旧动态 AID,再注册新列表并恢复接收。更新失败时插件会进入明确错误状态,不会虚报旧 AID 已恢复。

API

API 说明
getHceStatus(options) 获取能力、NFC、权限、状态、AID 和待响应请求快照
getHceDiagnostics(options) 获取插件版本、构建、宿主状态和最近原生错误
startHce(options) 启动 testApplet、custom;HarmonyOS 还支持 ndefTag
updateHceAids(options) 运行中动态替换 AID
stopHce(options?) 幂等停止并释放资源
onApduCommand(listener) 监听读卡器 APDU
offApduCommand(listener?) 按函数身份或全部取消 APDU 监听
sendApduResponse(options) 响应当前 custom 请求
onHceStateChange(listener) 监听 stopped/starting/running/updating/stopping/error
offHceStateChange(listener?) 取消状态监听
getTestAppletData(options) 读取内置 Applet 数据
setTestAppletData(options) 设置内置 Applet 数据
resetTestAppletData(options) 恢复默认测试数据

完整字段类型位于 utssdk/interface.uts。所有一次性 API 遵循单终态:成功时依次调用 success、complete,失败时依次调用 fail、complete。

错误码

错误码 说明
9071001 当前平台或设备不支持 HCE
9071002 应用上下文或宿主 HCE 组件标识不可用
9071003 卡模拟权限或宿主配置缺失
9071004 NFC 尚未开启
9071005 AID 参数不合法
9071006 APDU 或响应 APDU 不合法
9071007 当前生命周期状态不允许操作
9071008 APDU 请求过期、不存在或已响应
9071009 自定义 APDU 响应超时
9071010 原生 HCE 状态异常或发送失败
9071011 页面监听回调异常,已被插件隔离

与 lizhao-nfc-pro 双机测试

需要两台支持 NFC 的设备:

  1. 卡端 Android 或鸿蒙手机安装包含当前源码与 HCE 原生声明的新 APK/HAP。
  2. 在卡端启动 lizhao-hce-card;custom 模式保持页面监听存活,testApplet 可按业务需要在后台运行。
  3. 另一台设备用 lizhao-nfc-pro 开始读卡会话。
  4. 发现标签后通过 isoDepTransceive() 发送默认 SELECT。
  5. 收到 ...9000 后依次发送 GET、PUT、ECHO、CLEAR,并核对响应。
  6. 切换 custom 模式,验证 requestId 响应、超时 6400 和过期请求拒绝。
  7. 更新 AID 后确认旧 AID 返回 6A82,新 AID 可以 SELECT。

验证 Android lizhao-nfc-pro 的 NDEF 读写时,卡端必须使用 HarmonyOS ndefTag:

  1. 鸿蒙端启动 ndefTag,Android 端保持 skipNdefCheck=false。
  2. Android 调用 getNdefStatus(),应识别为可写 NDEF,容量约 2046 字节。
  3. 调用 writeNdefOnce() 写入新的 Text/URI 记录,并启用写后校验。
  4. 两台手机移开,再次贴合后调用 readNdefOnce(),应读回刚写入的记录。
  5. 再写入另一条不同内容并重复第 4 步,确认不是只成功一次。

这条链路可以覆盖 NDEF 检测、读取、写入、写后校验和重复贴合;不能覆盖 NdefFormatable、永久只读、MIFARE 技术族或真实标签断电保存。

单机能证明启动、权限、动态 AID 生命周期和无崩溃;只有双机贴卡才能证明真实射频 ISO-DEP/APDU 闭环。

FAQ

打开 NFC 后为什么另一个手机读不到?

必须先调用 startHce()。testApplet/custom 需要读卡器按 ISO-DEP 发送 SELECT/APDU;若需要让 Android 的标准 NDEF API 自动识别,鸿蒙端请选择 ndefTag。

为什么动态 AID 已传入,仍需要宿主静态声明?

系统必须先从安装包识别 HCE 服务。HarmonyOS 由 EntryAbility metadata 提供静态默认 AID;Android 由 HostApduService metadata 提供内部占位 AID。业务 AID 仍分别由 HceService.start 或 CardEmulation.registerAidsForService 动态注册。

修改后需要重新打包吗?

需要。Android 新增 Kotlin、Manifest、Service 和 AID XML,必须重新制作并安装匹配源码的 Android 自定义基座或正式 APK;HarmonyOS 新增或修改 ETS、HAR 权限、宿主 module.json5、AID metadata 时必须重新构建 HAP。仅更新 wgt/appResource 不能替换已经进入安装包的原生实现和声明。

支持支付卡模拟吗?

不支持。本插件使用 other-aid,只面向开发测试和自定义业务协议,不声明支付 AID,也不提供安全芯片或支付认证能力。

隐私、权限声明

1. 本插件需要申请的系统权限列表:

Android: android.permission.NFC;HarmonyOS: ohos.permission.NFC_CARD_EMULATION

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

处理读卡器主动发送的 APDU,以及调用方设置的测试数据和进程内 NDEF 内容;不读取通讯录、相册等用户数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

无

暂无用户评论。