40 NFC Tag 读写与高级应用:@ohos.nfc 模块实战
·
40 NFC Tag 读写与高级应用:@ohos.nfc 模块实战
前言

图:40 NFC Tag 读写与高级应用:@ohos.nfc 模块实战 运行效果截图(HarmonyOS NEXT)
上一篇文章我们通过 TapSharePage 和 BindRelationPage 了解了 NFC 碰一碰的 UI 交互设计。但实际的 NFC 通信远不止 UI 动画——它涉及 Tag 发现、NDEF 数据读写、NFC 控制器管理 等一系列底层 API。
本文以"鹿鹿·笔迹心理分析"项目中的 NFC 应用场景为背景,深入解析鸿蒙 @ohos.nfc 系列模块的核心 API 和 Tag 读写实战。
鸿蒙官方·NFC Tag 文档:developer.huawei.com
项目源码仓库:harmony-app GitHub

图:鸿蒙 NFC Tag 读写架构——NDEF 数据格式 + TagSession 读写 API
一、鸿蒙 NFC 模块总览
1.1 四大 NFC 模块
| 模块 | 用途 | 导入方式 |
|---|---|---|
@ohos.nfc.tag |
Tag 标签发现与读写 | import { tag } from '@kit.ConnectedTagKit' |
@ohos.nfc.controller |
NFC 开关控制与状态查询 | import { nfcController } from '@kit.ConnectedTagKit' |
@ohos.nfc.cardEmulation |
卡模拟(HCE) | import { cardEmulation } from '@kit.ConnectedTagKit' |
@ohos.rpc |
跨设备 RPC 数据传输 | import { rpc } from '@kit.IPCKit' |
1.2 项目中的 NFC 场景映射
| 场景 | 对应模块 | 实现方式 |
|---|---|---|
| 碰一碰分享(TapSharePage) | @ohos.nfc.tag + NDEF |
写入 NDEF 消息 → 对方读取 |
| 关系绑定(BindRelationPage) | @ohos.nfc.controller |
检测 NFC 开启 → 触发绑定流程 |
| 超级终端推送 | @ohos.rpc |
跨设备数据传输通道 |
二、NFC 控制器管理
2.1 检查 NFC 状态
import { nfcController } from '@kit.ConnectedTagKit'
function checkNfcStatus(): void {
// 查询 NFC 是否开启
const isNfcOpen = nfcController.isNfcOpen()
hilog.info(0x0000, 'NfcManager', 'NFC 状态: %{public}s', isNfcOpen ? '开启' : '关闭')
// 查询 NFC 是否可用(硬件支持)
const isNfcAvailable = nfcController.isNfcAvailable()
hilog.info(0x0000, 'NfcManager', 'NFC 可用: %{public}s', isNfcAvailable)
return { isNfcOpen, isNfcAvailable }
}
2.2 开启/关闭 NFC
import { nfcController } from '@kit.ConnectedTagKit'
import { common } from '@kit.AbilityKit'
async function toggleNfc(context: common.UIAbilityContext): Promise<void> {
try {
const isOpen = nfcController.isNfcOpen()
if (!isOpen) {
// 打开 NFC——需要系统权限
await nfcController.openNfc()
hilog.info(0x0000, 'NfcManager', 'NFC 已开启')
} else {
await nfcController.closeNfc()
hilog.info(0x0000, 'NfcManager', 'NFC 已关闭')
}
} catch (error) {
hilog.error(0x0000, 'NfcManager', '切换 NFC 失败: %{public}s', JSON.stringify(error))
}
}
2.3 注册 NFC 状态监听
import { nfcController } from '@kit.ConnectedTagKit'
// 注册 NFC 状态变化监听
nfcController.on('nfcStateChange', (state: number) => {
switch (state) {
case nfcController.NfcState.STATE_OFF:
hilog.info(0x0000, 'NfcManager', 'NFC 已关闭')
break
case nfcController.NfcState.STATE_ON:
hilog.info(0x0000, 'NfcManager', 'NFC 已开启')
break
case nfcController.NfcState.STATE_TURNING_ON:
hilog.info(0x0000, 'NfcManager', 'NFC 正在开启')
break
case nfcController.NfcState.STATE_TURNING_OFF:
hilog.info(0x0000, 'NfcManager', 'NFC 正在关闭')
break
default:
break
}
})
// 取消监听
nfcController.off('nfcStateChange')
三、Tag 标签发现
3.1 TagInfo 对象
当设备靠近 NFC 标签时,系统会分发一个 TagInfo 对象,包含标签的元信息:
import { tag } from '@kit.ConnectedTagKit'
// TagInfo 结构
interface TagInfo {
tagId: number[] // 标签 UID
technology: number[] // 支持的标签技术
extras?: Record<string, any> // 扩展数据
}
3.2 标签发现回调
import { tag } from '@kit.ConnectedTagKit'
// 注册 Tag 发现回调
tag.on('tagDiscovery', (tagInfo: tag.TagInfo) => {
hilog.info(0x0000, 'NfcManager', '发现 NFC 标签! UID: %{public}s', tagInfo.tagId.toString())
// 根据标签技术类型处理
const techs = tagInfo.technology
for (const tech of techs) {
switch (tech) {
case tag.NfcTechnology.NDEF:
hilog.info(0x0000, 'NfcManager', '支持 NDEF')
handleNdefTag(tagInfo)
break
case tag.NfcTechnology.NFC_A:
hilog.info(0x0000, 'NfcManager', 'NFC-A 类型')
break
case tag.NfcTechnology.NFC_B:
hilog.info(0x0000, 'NfcManager', 'NFC-B 类型')
break
case tag.NfcTechnology.NFC_F:
hilog.info(0x0000, 'NfcManager', 'NFC-F 类型(Felica)')
break
case tag.NfcTechnology.NFC_V:
hilog.info(0x0000, 'NfcManager', 'NFC-V 类型')
break
default:
break
}
}
})
// 取消标签发现监听
tag.off('tagDiscovery')
3.3 标签失焦
import { tag } from '@kit.ConnectedTagKit'
tag.on('tagLost', () => {
hilog.info(0x0000, 'NfcManager', '标签已离开感应区')
// 清理 UI 状态
// 重置分享动画
})
tag.off('tagLost')
四、NDEF 数据读写
4.1 NDEF 消息格式
NDEF(NFC Data Exchange Format)是 NFC 标签中最常用的数据格式:
+----------------------+
| NDEF Message |
| +----------------+ |
| | NDEF Record 1 | | ← TNF + Type + ID + Payload
| +----------------+ |
| +----------------+ |
| | NDEF Record 2 | | ← 可多个 Record
| +----------------+ |
+----------------------+
TNF(Type Name Format)类型:
| TNF 值 | 名称 | 说明 |
|---|---|---|
| 0x00 | TNF_EMPTY | 空记录 |
| 0x01 | TNF_WELL_KNOWN | RTD 类型(如 URI、Text) |
| 0x02 | TNF_MIME_MEDIA | MIME 类型 |
| 0x03 | TNF_ABSOLUTE_URI | 绝对 URI |
| 0x04 | TNF_EXTERNAL_TYPE | 自定义外部类型 |
4.2 读取 NDEF 数据
import { tag } from '@kit.ConnectedTagKit'
function handleNdefTag(tagInfo: tag.TagInfo): void {
// 获取 NDEF 标签对象
const ndefTag = tag.getNdefTag(tagInfo)
if (!ndefTag) {
hilog.error(0x0000, 'NfcManager', '非 NDEF 标签')
return
}
try {
// 连接标签
ndefTag.connect()
// 读取 NDEF 消息
const ndefMsg = ndefTag.readNdef()
if (ndefMsg && ndefMsg.length > 0) {
for (let i = 0; i < ndefMsg.length; i++) {
const record = ndefMsg[i]
hilog.info(0x0000, 'NfcManager',
'记录 %{public}d: tnf=%{public}d type=%{public}s payload=%{public}s',
i, record.tnf, record.type, record.payload)
}
}
// 关闭连接
ndefTag.close()
} catch (error) {
hilog.error(0x0000, 'NfcManager', '读取 NDEF 失败: %{public}s', JSON.stringify(error))
}
}
4.3 写入 NDEF 数据
import { tag } from '@kit.ConnectedTagKit'
/**
* 写入笔迹分析分享数据到 NFC 标签
*/
async function writeShareDataToTag(
tagInfo: tag.TagInfo,
shareData: string
): Promise<boolean> {
const ndefTag = tag.getNdefTag(tagInfo)
if (!ndefTag) return false
try {
ndefTag.connect()
// 检查标签是否可写
if (!ndefTag.isNdefWritable()) {
hilog.warn(0x0000, 'NfcManager', '标签不可写')
ndefTag.close()
return false
}
// 构建 NDEF 消息
// 使用外部类型记录格式:lulu.com:share
const records: tag.NdefRecord[] = [
{
tnf: 0x04, // TNF_EXTERNAL_TYPE
type: stringToBytes('lulu.com:share'),
id: [],
payload: stringToBytes(shareData)
}
]
// 写入标签
ndefTag.writeNdef(records)
hilog.info(0x0000, 'NfcManager', '写入 NDEF 成功: %{public}s', shareData)
ndefTag.close()
return true
} catch (error) {
hilog.error(0x0000, 'NfcManager', '写入 NDEF 失败: %{public}s', JSON.stringify(error))
return false
}
}
// 辅助:字符串 ↔ 字节数组
function stringToBytes(str: string): number[] {
const encoder = new TextEncoder()
return Array.from(encoder.encodeInto(str))
}
function bytesToString(bytes: number[]): string {
const decoder = new TextDecoder()
return decoder.decode(new Uint8Array(bytes))
}
4.4 读取现有 NDEF 消息
import { tag } from '@kit.ConnectedTagKit'
import { fileIo as fs } from '@kit.CoreFileKit'
/**
* 检查标签是否包含已存在的 NDEF 数据
*/
function checkExistingNdef(ndefTag: tag.NdefTag): void {
try {
ndefTag.connect()
// 查询 NDEF 标签的最大大小
const maxSize = ndefTag.getNdefMaxSize()
hilog.info(0x0000, 'NfcManager', '标签最大容量: %{public}d 字节', maxSize)
// 检查是否已格式化
const isFormatted = ndefTag.isNdefFormatted()
hilog.info(0x0000, 'NfcManager', '已格式化: %{public}s', isFormatted)
ndefTag.close()
} catch (error) {
hilog.error(0x0000, 'NfcManager', '查询 NDEF 失败: %{public}s', JSON.stringify(error))
}
}
五、NFC 在碰一碰分享中的完整流程
5.1 发送端流程
发送端(TapSharePage)
│
├── ① 检查 NFC 状态(nfcController.isNfcOpen)
│
├── ② 准备分享数据(笔迹分析结果 JSON)
│
├── ③ 注册 tagDiscovery 监听
│
├── ④ 显示脉冲动画("靠近对方手机")
│
├── ⑤ Tag 发现 → 获取 NdefTag
│
├── ⑥ 构建 NDEF 消息 + writeNdef
│
└── ⑦ 显示"分享成功" ✅
5.2 接收端流程
接收端
│
├── ① NFC 开启状态下靠近设备
│
├── ② tagDiscovery 触发
│
├── ③ 获取 NdefTag → readNdef
│
├── ④ 解析 NDEF Record → 提取数据
│
└── ⑤ 展示收到的内容(UI 更新)
5.3 代码实现蓝图
import { tag, nfcController } from '@kit.ConnectedTagKit'
import { hilog } from '@kit.PerformanceAnalysisKit'
class NfcShareService {
private tagInfo: tag.TagInfo | null = null
private ndefTag: tag.NdefTag | null = null
/**
* 初始化 NFC 分享服务
*/
init(): void {
// 注册标签发现
tag.on('tagDiscovery', (info: tag.TagInfo) => {
this.onTagDiscovered(info)
})
tag.on('tagLost', () => {
this.onTagLost()
})
}
/**
* Tag 发现回调
*/
private onTagDiscovered(tagInfo: tag.TagInfo): void {
this.tagInfo = tagInfo
const ndef = tag.getNdefTag(tagInfo)
if (ndef) {
this.ndefTag = ndef
// 触发业务回调
this.onNfcDetected?.()
}
}
/**
* Tag 丢失回调
*/
private onTagLost(): void {
this.tagInfo = null
this.ndefTag = null
this.onNfcLost?.()
}
// 业务回调
onNfcDetected?: () => void
onNfcLost?: () => void
/**
* 写入分享数据
*/
async writeShareData(data: string): Promise<boolean> {
if (!this.ndefTag) return false
return writeShareDataToTag(this.tagInfo!, data)
}
/**
* 读取分享数据
*/
async readShareData(): Promise<string | null> {
if (!this.ndefTag) return null
try {
this.ndefTag.connect()
const ndefMsg = this.ndefTag.readNdef()
this.ndefTag.close()
if (ndefMsg && ndefMsg.length > 0) {
const record = ndefMsg[0]
return bytesToString(record.payload)
}
return null
} catch (error) {
hilog.error(0x0000, 'NfcShareService', '读取失败: %{public}s', JSON.stringify(error))
return null
}
}
/**
* 销毁服务
*/
destroy(): void {
tag.off('tagDiscovery')
tag.off('tagLost')
}
}
六、NFC 权限配置
6.1 module.json5 声明
{
"requestPermissions": [
{
"name": "ohos.permission.NFC_TAG",
"reason": "$string:nfc_tag_permission_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
},
{
"name": "ohos.permission.NFC_CARD_EMULATION",
"reason": "$string:nfc_card_emulation_permission_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
}
6.2 权限说明
| 权限 | 必要性 | 用途 |
|---|---|---|
ohos.permission.NFC_TAG |
✅ 必需 | 读取和写入 NFC 标签 |
ohos.permission.NFC_CARD_EMULATION |
❌ 可选(HCE 场景) | 模拟为 NFC 卡片 |
| 控制 NFC 开关 | 无需权限 | 系统设置控制 |
七、NFC 标签类型与选型
7.1 常见 NFC 标签对比
| 标签类型 | 容量 | 通信协议 | 写入次数 | 典型用途 | 成本 |
|---|---|---|---|---|---|
| NTAG213 | 144 字节 | NFC-A | 10万+ | 小型数据分享 | 低 |
| NTAG215 | 504 字节 | NFC-A | 10万+ | 游戏角色/Amiibo | 低 |
| NTAG216 | 888 字节 | NFC-A | 10万+ | URL/名片 | 低 |
| MIFARE Classic 1K | 1024 字节 | NFC-A | 10万+ | 门禁/公交 | 中 |
| MIFARE DESFire | 8KB | NFC-A | 50万+ | 安全支付 | 高 |
| Type B | 可变 | NFC-B | 高 | 身份证件 | 高 |
7.2 项目推荐选型
对于"鹿鹿"App 的分享场景,推荐使用 NTAG216(888 字节):
// NTAG216 容量分析
const shareData = JSON.stringify({
userId: 'LU8847',
reportId: 42,
radar: { energy: 62, thinking: 65, action: 55, emotion: 72, social: 60, detail: 70 },
personalityType: '温和理性型',
timestamp: 1749876543000
})
// JSON 字符串约 200-300 字节
// NTAG216 容量 888 字节 → 完全够用
// 还可写入额外的 URL 和文本信息
八、NFC 安全注意事项
8.1 数据加密
/**
* NFC 数据安全建议:
*
* 1. 敏感数据不上 Tag
* 标签中只存储引用 ID,详细数据通过数据库查询
*
* 2. 添加签名验证
* 写入时附加 HMAC 签名,读取时验证完整性
*
* 3. 时效性控制
* 分享数据过期时间:默认 30 分钟
*/
interface NfcSecureData {
userId: number
timestamp: number
expiry: number // 过期时间戳
signature: string // HMAC 签名
}
function buildSecurePayload(userId: number): NfcSecureData {
const now = Date.now()
return {
userId,
timestamp: now,
expiry: now + 30 * 60 * 1000, // 30 分钟过期
signature: '' // 实际应使用密钥计算 HMAC
}
}
8.2 防重放攻击
/**
* 防重放机制:
*
* 1. 每次写入生成随机 nonce
* 2. 服务端记录已使用 nonce 列表
* 3. 过期时间超出则不处理
*/
function generateNonce(): string {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
let nonce = ''
for (let i = 0; i < 8; i++) {
nonce += chars.charAt(Math.floor(Math.random() * chars.length))
}
return nonce
}
九、NFC 调试技巧
9.1 日志追踪
const TAG = 'NfcDebug'
// 在每个 NFC 操作节点添加日志
function nfcDebugLog(operation: string, data?: string): void {
hilog.info(0x0000, TAG, '[NFC] %{public}s | NFC状态=%{public}s | %{public}s',
operation,
nfcController.isNfcOpen() ? '开' : '关',
data ?? ''
)
}
9.2 常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 标签发现不触发 | NFC 未开启 | nfcController.isNfcOpen() 前置检查 |
| 写入失败 | 标签只读/锁定 | ndefTag.isNdefWritable() 预检 |
| 读取为空 | 标签未格式化 | ndefTag.isNdefFormatted() 检查 |
| 连接超时 | 距离太远/移动太快 | 保持设备靠近 2-3 秒 |
总结
本文深入解析了鸿蒙 NFC Tag 读写的核心技术:
- NFC 模块体系:
nfcController(控制器)+tag(标签发现)+ NDEF(数据读写) - Tag 发现流程:
tag.on('tagDiscovery')→tag.getNdefTag()→connect()→readNdef()/writeNdef() - NDEF 数据格式:TNF + Type + ID + Payload 四段式结构
- 安全策略:减少敏感数据上 Tag、时效性控制、防重放攻击
- 完整流程:从 NFC 状态检查到分享数据写入的保姆级蓝图
下一篇文章将介绍 二维码生成与扫描——通过 Scan Kit 实现另一种短距分享方式。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
参考资源:
更多推荐




所有评论(0)