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 读写架构示意图

图:鸿蒙 NFC Tag 读写架构——NDEF 数据格式 + TagSession 读写 API

NDEF 格式

非 NDEF 格式

NFC Tag 靠近

TagSession.getTag

Tag 类型检测

NdefTag.readNdef

NfcATag/IsoDep 等底层读写

解析 NdefRecord
TNF + 类型 + Payload

提取文本/URI/自定义数据

原始字节读写

NdefTag.writeNdef
写入新数据

一、鸿蒙 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 读写的核心技术:

  1. NFC 模块体系nfcController(控制器)+ tag(标签发现)+ NDEF(数据读写)
  2. Tag 发现流程tag.on('tagDiscovery')tag.getNdefTag()connect()readNdef()/writeNdef()
  3. NDEF 数据格式:TNF + Type + ID + Payload 四段式结构
  4. 安全策略:减少敏感数据上 Tag、时效性控制、防重放攻击
  5. 完整流程:从 NFC 状态检查到分享数据写入的保姆级蓝图

下一篇文章将介绍 二维码生成与扫描——通过 Scan Kit 实现另一种短距分享方式。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


参考资源:

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐