HarmonyOS APP<<古今职鉴定>>开源教程第14篇:碰一碰分享:NFC 近场通信

本篇学习 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 开发流程

  1. 声明权限
  2. 检查 NFC 状态
  3. 监听标签发现
  4. 读取/写入数据
  5. 关闭连接

课后练习

练习1:实现名片接收

接收其他设备发送的官职名片并显示。

练习2:添加历史记录

记录分享和接收的名片历史。


下一课预告

第15课我们将学习小艺智能体,包括:

  • Intents Kit 意图框架
  • 语音交互集成
  • 智能问答能力

项目开源地址

https://gitcode.com/daleishen/gujinzhijian

相关推荐
想你依然心痛2 小时前
HarmonyOS 6(API 23)实战:基于悬浮导航、沉浸光感与HMAF的“智流工坊“——低代码可视化智能体编排平台
低代码·华为·harmonyos
richard_yuu2 小时前
鸿蒙ArkUI组件化实战|公共组件封装、复用解耦与上架级UI规范落地
ui·华为·harmonyos
AI周红伟2 小时前
Token工厂落地:移动,电信,华为,阿里,从流量到Token,All in Token
大数据·人工智能·百度·华为·copilot·openclaw
KKei16382 小时前
Flutter for OpenHarmony 学习专注模式APP技术文章
学习·flutter·华为·harmonyos
想你依然心痛3 小时前
HarmonyOS 6(API 23)实战:基于悬浮导航、沉浸光感与HMAF的“数字孪生工坊“——工业制造AI智能体协同平台
人工智能·制造·harmonyos
UnicornDev3 小时前
【Flutter x HarmonyOS 6】挑战功能的业务逻辑实现
flutter·华为·harmonyos·鸿蒙·鸿蒙系统
不爱吃糖的程序媛3 小时前
Harmonybrew:让Homebrew落地OpenHarmony,补齐鸿蒙命令行包管理能力
华为·harmonyos
learning-striving4 小时前
华为云欧拉操作系统的服务器实例中手工部署 Docker
linux·运维·服务器·docker·容器·华为云
nashane17 小时前
HarmonyOS 6学习:AI攻略长截图“防抖”与像素级拼接术
学习·华为·harmonyos