HarmonyOS 6.0 NFC标签读写与碰一碰连接——物联网应用的近场入口
·
手机碰一碰 NFC 标签就弹出配置页、碰一碰配网 WiFi、碰一碰开门禁——NFC 是智能家居和 IoT 场景的关键入口。HarmonyOS NEXT 提供了完整的 NFC API:标签识别、NDEF 读写、前台分发优先注册。这篇把 NFC 近场通信的核心用法讲清楚。
NFC 通信概览
NFC(近场通信)的核心概念:
- Tag(标签)——NFC 贴纸、NFC 卡片等被动设备
- NDEF——NFC 数据交换格式,标准化的数据组织方式
- 前台分发——APP 在前台时优先接收 NFC 意图
- AAR——Android Application Record,碰一碰自动打开指定 APP
import { tag } from '@kit.ConnectivityKit'
import { ndef } from '@kit.ConnectivityKit'
权限配置
{
"requestPermissions": [
{ "name": "ohos.permission.NFC_TAG" },
{ "name": "ohos.permission.NFC_ADAPTER" }
]
}
标签检测
标签靠近手机时,系统通过 Ability 的 onNewWant 分发 NFC 意图。
// EntryAbility.ets
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
let tagInfo: tag.TagInfo | null = null
try {
tagInfo = tag.getTagInfo(want)
} catch (e) {
return
}
if (tagInfo === null) return
// 判断标签类型
if (tag.isNdef(tagInfo)) {
let ndefTag:ndef.NdefTag = ndef.getNdefTag(tagInfo)
// 读取 NDEF 内容
}
}
要点: getTagInfo 从 want 中提取标签信息,isNdef 判断是否为 NDEF 格式标签。非 NDEF 标签(如 MifareClassic)用对应的 getXXXTag 方法。
NDEF 读写
NDEF 是最常用的 NFC 数据格式——类似"JSON for NFC"。
读取 NDEF
let ndefTag: ndef.NdefTag = ndef.getNdefTag(tagInfo)
let ndefMessage: ndef.NdefMessage = ndefTag.getNdefMessage()
let records: ndef.NdefRecord[] = ndefMessage.getRecords()
for (let i: number = 0; i < records.length; i++) {
let record: ndef.NdefRecord = records[i]
let payload: string = record.payload as string
let recordType: string = record.recordType as string
// URI 类型: recordType = 'U'
// 文本类型: recordType = 'T'
}
写入 NDEF
// 创建 NDEF 消息
let uriRecord: ndef.NdefRecord = ndef.createUriRecord('https://harmonyos.com')
let ndefMsg: ndef.NdefMessage = ndef.createNdefMessage([uriRecord])
// 写入标签
ndefTag.writeNdef(ndefMsg)
创建不同类型的 NDEF Record
// URI 记录
let uri: ndef.NdefRecord = ndef.createUriRecord('https://example.com')
// 文本记录
let text: ndef.NdefRecord = ndef.createTextRecord('zh', '你好HarmonyOS')
// MIME 记录
let mime: ndef.NdefRecord = ndef.createMimeRecord('application/json', '{"key":"value"}')
// AAR 记录(碰一碰打开APP)
let aar: ndef.NdefRecord = ndef.createAarRecord('com.example.myapp')
要点: AAR 记录让手机碰标签时自动打开指定包名的 APP,实现"碰一碰直连"。
前台分发
当多个 APP 都能处理 NFC 标签时,前台分发让当前 APP 优先接收。
import { tag } from '@kit.ConnectivityKit'
// 注册前台分发
let elementName: ElementName = {
bundleName: 'com.example.myapp',
abilityName: 'EntryAbility'
}
let techList: string[][] = [
['ndef', 'ndef-formatable']
]
tag.registerForegroundDispatch(elementName, techList)
// 取消前台分发
tag.unregisterForegroundDispatch(elementName)
要点: registerForegroundDispatch 在 Ability 的 onForeground 注册,onBackground 取消。techList 指定感兴趣的标签技术类型。
NFC 碰一碰场景模拟 Demo
由于 NFC 需要真机 + NFC 标签,这里用模拟方式展示完整交互流程。
interface NfcRecord {
id: string
type: string
content: string
time: string
}
@Entry
@Component
struct NfcSimDemo {
@State nfcRecords: NfcRecord[] = []
@State tagDetected: boolean = false
@State currentTagType: string = ''
@State ndefContent: string = ''
@State writeInput: string = 'https://harmonyos.com'
@State statusMsg: string = '将NFC标签靠近手机...'
build() {
Column({ space: 16 }) {
Text('NFC 碰一碰模拟')
.fontSize(22)
.fontWeight(FontWeight.Bold)
.width('100%')
Column() {
Column()
.width(100)
.height(100)
.borderRadius(50)
.backgroundColor(this.tagDetected ? '#E8F5E9' : '#F5F5F5')
.border({ width: 3, color: this.tagDetected ? '#4CAF50' : '#E0E0E0', style: BorderStyle.Dashed })
Text(this.tagDetected ? '标签已检测' : '等待NFC标签')
.fontSize(14)
.fontColor(this.tagDetected ? '#4CAF50' : '#999999')
.margin({ top: 8 })
}
.width('100%')
.alignItems(HorizontalAlign.Center)
.padding(16)
Row({ space: 8 }) {
Button('模拟标签靠近').onClick(() => {
this.tagDetected = true
this.currentTagType = 'NDEF'
this.ndefContent = 'https://harmonyos.com'
this.addRecord('检测', '发现NDEF标签')
this.statusMsg = '标签已检测到(模拟)'
})
Button('模拟标签移开').onClick(() => {
this.tagDetected = false
this.ndefContent = ''
this.statusMsg = '标签已移开'
this.addRecord('移开', '标签离开')
})
}
if (this.tagDetected) {
Column({ space: 8 }) {
Text('标签信息')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.width('100%')
Row() {
Text('类型:').fontSize(14).fontColor('#666666').width(60)
Text(this.currentTagType).fontSize(14).fontWeight(FontWeight.Medium)
}
Row() {
Text('NDEF:').fontSize(14).fontColor('#666666').width(60)
Text(this.ndefContent || '(空)').fontSize(14).fontColor('#1a73e8')
}
TextInput({ text: this.writeInput, placeholder: '写入内容' })
.onChange((value: string) => { this.writeInput = value })
Row({ space: 8 }) {
Button('写入NDEF').onClick(() => {
this.ndefContent = this.writeInput
this.addRecord('写入', this.writeInput)
})
Button('读取NDEF').onClick(() => {
this.addRecord('读取', this.ndefContent || '(空)')
})
Button('清空').onClick(() => {
this.ndefContent = ''
this.addRecord('清空', 'NDEF已清除')
})
}
}
.width('100%')
.padding(16)
.borderRadius(12)
.backgroundColor('#FFFFFF')
}
// 操作记录
if (this.nfcRecords.length > 0) {
Text('操作记录:')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.width('100%')
ForEach(this.nfcRecords.slice().reverse(), (record: NfcRecord) => {
Row({ space: 8 }) {
Text(record.time).fontSize(11).fontColor('#999999').width(60)
Text(record.type).fontSize(12).fontWeight(FontWeight.Medium).fontColor('#1a73e8').width(50)
Text(record.content).fontSize(12).fontColor('#333333').layoutWeight(1)
}
.padding(4)
}, (record: NfcRecord) => record.id)
}
Text(this.statusMsg)
.fontSize(12)
.fontColor('#999999')
}
.width('100%')
.padding(20)
}
private addRecord(type: string, content: string): void {
this.nfcRecords.push({
id: Date.now().toString(),
type: type,
content: content,
time: new Date().toLocaleTimeString()
})
}
}
碰一碰配网实战
最常见的 IoT 场景——手机碰一碰设备上的 NFC 标签,自动跳转配网页面,输入 WiFi 密码后下发给设备。
// NDEF 标签中写入的内容
// URI: https://myapp.com/setup?device=ABC123
// AAR: com.example.iotapp
// APP 收到 NFC 意图后解析
onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
let tagInfo: tag.TagInfo | null = tag.getTagInfo(want)
if (tagInfo === null) return
if (tag.isNdef(tagInfo)) {
let ndefTag: ndef.NdefTag = ndef.getNdefTag(tagInfo)
let msg: ndef.NdefMessage = ndefTag.getNdefMessage()
let records: ndef.NdefRecord[] = msg.getRecords()
// 解析 URI 中的设备 ID
// 跳转到配网页面
let uri: string = records[0].payload as string
let deviceId: string = this.parseDeviceId(uri)
router.pushUrl({ url: 'pages/WifiSetup', params: { deviceId: deviceId } })
}
}
标签类型判断
不同标签支持不同的技术:
| 标签类型 | 判断方法 | 适用场景 |
|---|---|---|
| NDEF | tag.isNdef() | 通用数据读写 |
| MifareClassic | tag.isMifareClassic() | 门禁卡、公交卡 |
| MifareUltralight | tag.isMifareUltralight() | NFC 贴纸 |
| NfcA | tag.isNfcA() | ISO 14443-3A |
| NfcB | tag.isNfcB() | ISO 14443-3B |
| NfcF | tag.isNfcF() | FeliCa |
| NfcV | tag.isNfcV() | ISO 15693 |
要点: 先判断标签类型,再用对应的 API 操作。NDEF 是最通用的格式,优先使用。
踩坑清单
| 问题 | 原因 | 解决 |
|---|---|---|
| getTagInfo 返回 null | want 不是 NFC 意图 | 检查 intent 来源 |
| isNdef 返回 false | 标签不是 NDEF 格式 | 空白标签先格式化 |
| writeNdef 报错 | 标签只读或容量不足 | 检查标签是否可写 |
| 前台分发不生效 | 未在 onForeground 注册 | onForeground 注册、onBackground 取消 |
| 碰标签打开别的 APP | 未注册前台分发 | 注册前台分发 + AAR 记录 |
| NDEF 解析乱码 | 编码处理不对 | URI 类型用 UTF-8,文本注意语言码 |
| readNdef 返回空 | 标签无 NDEF 消息 | 先检查 NDEF 消息是否存在 |
| 标签检测延迟 | 系统轮询间隔 | 前台分发 + 注册 techList |
| 多标签干扰 | 同时靠近多个标签 | 一次只靠近一个标签 |
| createUriRecord 格式错 | URI 不完整 | 必须含协议前缀 https:// |
更多推荐
所有评论(0)