本篇学习 NFC 近场通信,实现碰一碰分享官职名片
图:古今职鉴开源教程封面。本篇围绕「碰一碰分享:NFC 近场通信」展开。
学习目标
完成本篇后,你将能够:
- ✅ 理解 NFC 技术原理
- ✅ 配置 NFC 权限
- ✅ 读写 NFC 标签
- ✅ 实现设备间数据传输
预计学习时间
约 90 分钟
实战一:理解 NFC 技术
第一步:什么是 NFC
NFC(Near Field Communication)近场通信:
- 工作距离:约 10cm 以内
- 通信频率:13.56MHz
- 数据传输速率:106-424 Kbps
第二步:NFC 工作模式
| 模式 | 说明 | 应用场景 |
|---|---|---|
| 读卡器模式 | 读取 NFC 标签 | 门禁卡、公交卡 |
| 卡模拟模式 | 模拟 NFC 卡 | 手机支付 |
| P2P 模式 | 设备间通信 | 碰一碰传输 |
第三步:鸿蒙 NFC 能力
- 读取/写入 NFC 标签
- 设备间数据传输
- 碰一碰分享
实战二:配置 NFC 权限
第一步:声明权限
在 module.json5 中添加:
json
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.NFC_TAG",
"reason": "$string:nfc_permission_reason",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
}
}
第二步:添加权限说明
在 resources/base/element/string.json 中:
json
{
"string": [
{
"name": "nfc_permission_reason",
"value": "用于读取NFC标签和碰一碰分享功能"
}
]
}
第三步:检查 NFC 状态
typescript
import { nfcController } from '@kit.ConnectivityKit';
function checkNfcState(): boolean {
try {
const state = nfcController.getNfcState();
return state === nfcController.NfcState.STATE_ON;
} catch (error) {
console.error('检查NFC状态失败:', error);
return false;
}
}
实战三:读取 NFC 标签
第一步:导入模块
typescript
import { tag } from '@kit.ConnectivityKit';
import { BusinessError } from '@kit.BasicServicesKit';
第二步:监听标签发现
typescript
@Entry
@Component
struct Lesson14Page {
@State tagInfo: string = '等待扫描NFC标签...';
@State nfcEnabled: boolean = false;
aboutToAppear() {
this.checkNfcState();
}
checkNfcState() {
try {
const state = nfcController.getNfcState();
this.nfcEnabled = state === nfcController.NfcState.STATE_ON;
} catch {
this.nfcEnabled = false;
}
}
}
第三步:处理标签数据
typescript
// 当系统检测到NFC标签时,会通过 Want 传递标签信息
// 在 EntryAbility 中处理
// EntryAbility.ets
onCreate(want: Want) {
if (want.action === 'ohos.nfc.tag.action.TAG_FOUND') {
// 获取标签信息
const tagInfo = tag.getTagInfo(want);
if (tagInfo) {
this.handleNfcTag(tagInfo);
}
}
}
handleNfcTag(tagInfo: tag.TagInfo) {
// 获取标签 UID
const uid = tagInfo.uid;
console.log('标签UID:', this.bytesToHex(uid));
// 获取支持的技术类型
const techList = tagInfo.technology;
console.log('支持的技术:', techList);
}
bytesToHex(bytes: number[]): string {
return bytes.map(b => b.toString(16).padStart(2, '0')).join(':');
}
实战四:写入 NFC 标签
第一步:创建 NDEF 消息
typescript
import { ndef } from '@kit.ConnectivityKit';
function createNdefMessage(text: string): ndef.NdefMessage {
// 创建文本记录
const textRecord = ndef.createTextRecord(text, 'zh');
// 创建 NDEF 消息
const ndefMessage = ndef.createNdefMessage([textRecord]);
return ndefMessage;
}
第二步:写入标签
typescript
async function writeNdefTag(tagInfo: tag.TagInfo, message: ndef.NdefMessage): Promise<boolean> {
try {
// 获取 NDEF 标签对象
const ndefTag = ndef.getNdef(tagInfo);
if (!ndefTag) {
console.error('不是NDEF标签');
return false;
}
// 连接标签
await ndefTag.connect();
// 检查是否可写
if (!ndefTag.isNdefWritable()) {
console.error('标签不可写');
await ndefTag.close();
return false;
}
// 写入数据
await ndefTag.writeNdef(message);
// 关闭连接
await ndefTag.close();
return true;
} catch (error) {
console.error('写入失败:', error);
return false;
}
}
第三步:读取标签内容
typescript
async function readNdefTag(tagInfo: tag.TagInfo): Promise<string | null> {
try {
const ndefTag = ndef.getNdef(tagInfo);
if (!ndefTag) return null;
await ndefTag.connect();
// 读取 NDEF 消息
const ndefMessage = await ndefTag.readNdef();
await ndefTag.close();
if (ndefMessage) {
// 解析记录
const records = ndefMessage.getNdefRecords();
if (records.length > 0) {
// 获取第一条记录的内容
const payload = records[0].payload;
// 解析文本(跳过语言代码)
const langCodeLen = payload[0];
const text = String.fromCharCode(...payload.slice(1 + langCodeLen));
return text;
}
}
return null;
} catch (error) {
console.error('读取失败:', error);
return null;
}
}
实战五:碰一碰分享官职名片
第一步:定义名片数据结构
typescript
interface OfficialCard {
name: string; // 官职名称
dynasty: string; // 朝代
rank: string; // 品级
description: string; // 职责描述
}
第二步:序列化名片数据
typescript
function serializeCard(card: OfficialCard): string {
return JSON.stringify(card);
}
function deserializeCard(data: string): OfficialCard | null {
try {
return JSON.parse(data) as OfficialCard;
} catch {
return null;
}
}
第三步:创建完整页面
typescript
import { nfcController } from '@kit.ConnectivityKit';
interface OfficialCard {
name: string;
dynasty: string;
rank: string;
description: string;
}
@Entry
@Component
struct Lesson14Page {
@State nfcEnabled: boolean = false;
@State statusMessage: string = '检查NFC状态...';
@State selectedCard: OfficialCard | null = null;
private officialCards: OfficialCard[] = [
{
name: '丞相',
dynasty: '秦',
rank: '正一品',
description: '百官之长,辅佐皇帝处理政务'
},
{
name: '太尉',
dynasty: '秦',
rank: '正一品',
description: '掌管全国军事'
},
{
name: '御史大夫',
dynasty: '秦',
rank: '从一品',
description: '监察百官,掌管图籍'
}
];
aboutToAppear() {
this.checkNfcState();
this.selectedCard = this.officialCards[0];
}
checkNfcState() {
try {
const state = nfcController.getNfcState();
this.nfcEnabled = state === nfcController.NfcState.STATE_ON;
this.statusMessage = this.nfcEnabled ? 'NFC已开启,可以碰一碰分享' : 'NFC未开启';
} catch {
this.nfcEnabled = false;
this.statusMessage = '设备不支持NFC';
}
}
build() {
Column() {
// 头部
Row() {
Text('碰一碰分享')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#1e293b')
}
.width('100%')
.height(56)
.padding({ left: 16, right: 16 })
.backgroundColor(Color.White)
Scroll() {
Column({ space: 20 }) {
// NFC 状态
Row({ space: 8 }) {
Circle()
.width(12)
.height(12)
.fill(this.nfcEnabled ? '#22c55e' : '#ef4444')
Text(this.statusMessage)
.fontSize(14)
.fontColor('#64748b')
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(12)
// 选择名片
Column({ space: 12 }) {
Text('选择要分享的官职名片')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#1e293b')
ForEach(this.officialCards, (card: OfficialCard, index: number) => {
Row() {
Column({ space: 4 }) {
Text(card.name)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#1e293b')
Text(`${card.dynasty} · ${card.rank}`)
.fontSize(13)
.fontColor('#64748b')
}
.alignItems(HorizontalAlign.Start)
.layoutWeight(1)
if (this.selectedCard?.name === card.name) {
Image($r('app.media.ic_check_circle'))
.width(24)
.height(24)
.fillColor('#c41e3a')
}
}
.width('100%')
.padding(12)
.backgroundColor(this.selectedCard?.name === card.name ? '#fef2f2' : '#f8f8f8')
.borderRadius(8)
.onClick(() => {
this.selectedCard = card;
})
})
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(12)
.alignItems(HorizontalAlign.Start)
// 名片预览
if (this.selectedCard) {
Column({ space: 12 }) {
Text('名片预览')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#1e293b')
Column({ space: 8 }) {
Text(this.selectedCard.name)
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#c41e3a')
Row({ space: 8 }) {
Text(this.selectedCard.dynasty)
.fontSize(12)
.fontColor(Color.White)
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
.backgroundColor('#c41e3a')
.borderRadius(4)
Text(this.selectedCard.rank)
.fontSize(12)
.fontColor('#c41e3a')
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
.backgroundColor('#fef2f2')
.borderRadius(4)
}
Text(this.selectedCard.description)
.fontSize(14)
.fontColor('#64748b')
.margin({ top: 8 })
}
.width('100%')
.padding(20)
.backgroundColor('#fffbeb')
.borderRadius(12)
.alignItems(HorizontalAlign.Center)
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(12)
.alignItems(HorizontalAlign.Start)
}
// 使用说明
Column({ space: 8 }) {
Text('使用方法')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor('#1e293b')
Text('1. 确保两台设备都开启了NFC')
.fontSize(13)
.fontColor('#64748b')
Text('2. 选择要分享的官职名片')
.fontSize(13)
.fontColor('#64748b')
Text('3. 将两台设备背靠背轻触')
.fontSize(13)
.fontColor('#64748b')
Text('4. 听到提示音后完成传输')
.fontSize(13)
.fontColor('#64748b')
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(12)
.alignItems(HorizontalAlign.Start)
}
.padding(16)
}
.layoutWeight(1)
}
.width('100%')
.height('100%')
.backgroundColor('#f8f6f5')
}
}
@Builder
export function Lesson14PageBuilder() {
Lesson14Page()
}
第四步:运行验证
bash
hvigorw assembleHap --no-daemon
本课小结
核心知识点
| 知识点 | 说明 |
|---|---|
| NFC 模式 | 读卡器、卡模拟、P2P |
| nfcController | NFC 状态管理 |
| tag 模块 | 标签读写操作 |
| ndef 模块 | NDEF 消息处理 |
| NdefMessage | NDEF 数据格式 |
NFC 开发流程
- 声明权限
- 检查 NFC 状态
- 监听标签发现
- 读取/写入数据
- 关闭连接
课后练习
练习1:实现名片接收
接收其他设备发送的官职名片并显示。
练习2:添加历史记录
记录分享和接收的名片历史。
下一课预告
第15课我们将学习小艺智能体,包括:
- Intents Kit 意图框架
- 语音交互集成
- 智能问答能力