HarmonyOS APP开发NFC标签读写:NdefMessage处理

举报
Jack20 发表于 2026/06/19 20:16:56 2026/06/19
【摘要】 HarmonyOS APP开发NFC标签读写:NdefMessage处理NFC(近场通信)技术已经渗透到我们生活的方方面面——刷地铁卡、门禁卡、手机支付,甚至产品标签扫码。今天咱们就来聊聊鸿蒙系统中的NFC标签读写,特别是NDEF消息的处理,看看如何让你的应用具备"碰一碰"的能力。 一、背景与动机 1.1 NFC技术概述NFC(Near Field Communication)是一种短距离...

HarmonyOS APP开发NFC标签读写:NdefMessage处理

NFC(近场通信)技术已经渗透到我们生活的方方面面——刷地铁卡、门禁卡、手机支付,甚至产品标签扫码。今天咱们就来聊聊鸿蒙系统中的NFC标签读写,特别是NDEF消息的处理,看看如何让你的应用具备"碰一碰"的能力。

一、背景与动机

1.1 NFC技术概述

NFC(Near Field Communication)是一种短距离高频无线通信技术,工作在13.56MHz频段:

对比项 NFC 蓝牙 WiFi
通信距离 < 10cm ~10m ~100m
建立时间 < 0.1s 数秒 数秒
功耗 极低 中等 较高
安全性 高(距离近)
典型应用 支付、门禁、标签 音频、文件 网络接入

NFC的三种工作模式

  1. 读写模式(Reader/Writer):手机读取或写入NFC标签
  2. 点对点模式(P2P):两台NFC设备间数据传输
  3. 卡模拟模式(Card Emulation):手机模拟成NFC卡片

本文重点讲解读写模式

1.2 NDEF消息格式

NDEF(NFC Data Exchange Format)是NFC论坛定义的标准数据格式:

图片.png

常见NDEF记录类型

类型 MIME类型 用途
Text text/plain 文本信息
URI URI 网址、电话等
Smart Poster smartposter 智能海报(包含URI和文本)
MIME 自定义 任意数据
AAR application/vnd.android.package-archive Android应用启动

二、核心原理

2.1 NFC标签读写流程

图片.png

2.2 标签类型与技术

NFC标签有多种类型,支持不同的技术:

// 标签类型
enum TagType {
  // NDEF格式标签
  NDEF = 'NdefTag',
  
  // Mifare系列
  MIFARE_CLASSIC = 'MifareClassicTag',
  MIFARE_ULTRALIGHT = 'MifareUltralightTag',
  
  // ISO标准
  ISO15693 = 'Iso15693Tag',
  ISO7816 = 'Iso7816Tag',
  
  // 其他
  NFC_A = 'NfcATag',
  NFC_B = 'NfcBTag',
  NFC_F = 'NfcFTag',
  NFC_V = 'NfcVTag'
}

// 标签技术能力
interface TagTechnology {
  // 是否支持NDEF
  isNdefSupported: boolean;
  
  // 是否可写
  isWritable: boolean;
  
  // 存储大小
  maxSize: number;
  
  // 已用大小
  usedSize: number;
}

2.3 NDEF记录结构详解

// TNF(Type Name Format)类型
enum Tnf {
  EMPTY = 0x00,           // 空记录
  WELL_KNOWN = 0x01,      // 已知类型(如URI、Text)
  MIME_MEDIA = 0x02,      // MIME类型
  ABSOLUTE_URI = 0x03,    // 绝对URI
  EXTERNAL = 0x04,        // 外部类型
  UNKNOWN = 0x05,         // 未知类型
  UNCHANGED = 0x06,       // 未改变(分块记录)
  RESERVED = 0x07         // 保留
}

// NDEF记录
interface NdefRecord {
  // 类型名称格式
  tnf: Tnf;
  
  // 类型
  type: ArrayBuffer;
  
  // 负载数据
  payload: ArrayBuffer;
  
  // 标识符(可选)
  id?: ArrayBuffer;
}

// NDEF消息
interface NdefMessage {
  // 记录数组
  records: NdefRecord[];
}

三、代码实战

3.1 NFC管理核心类封装

import nfc from '@ohos.nfc';
import { BusinessError } from '@ohos.base';

/**
 * NDEF记录工具类
 * 用于创建和解析NDEF记录
 */
export class NdefHelper {
  /**
   * 创建文本记录
   * @param text 文本内容
   * @param languageCode 语言代码,默认'en'
   */
  public static createTextRecord(text: string, languageCode: string = 'en'): nfc.NdefRecord {
    // 准备语言代码字节
    const langBytes = new TextEncoder().encode(languageCode);
    const textBytes = new TextEncoder().encode(text);
    
    // 构建payload: [语言代码长度 | 语言代码 | 文本]
    const payload = new ArrayBuffer(1 + langBytes.length + textBytes.length);
    const view = new Uint8Array(payload);
    
    view[0] = langBytes.length; // 语言代码长度
    view.set(langBytes, 1);     // 语言代码
    view.set(textBytes, 1 + langBytes.length); // 文本
    
    return {
      tnf: nfc.Tnf.WELL_KNOWN,
      type: new TextEncoder().encode('T'), // 'T'表示文本类型
      payload: payload
    };
  }

  /**
   * 解析文本记录
   */
  public static parseTextRecord(record: nfc.NdefRecord): string {
    if (record.tnf !== nfc.Tnf.WELL_KNOWN) {
      throw new Error('不是文本记录');
    }
    
    const typeStr = new TextDecoder().decode(record.type);
    if (typeStr !== 'T') {
      throw new Error('不是文本记录');
    }
    
    const payload = new Uint8Array(record.payload);
    const langLength = payload[0];
    
    // 跳过语言代码,读取文本
    const textBytes = payload.slice(1 + langLength);
    return new TextDecoder().decode(textBytes);
  }

  /**
   * 创建URI记录
   * @param uri URI地址
   */
  public static createUriRecord(uri: string): nfc.NdefRecord {
    // URI前缀缩写表
    const prefixes = [
      '',                     // 0x00: 无前缀
      'http://www.',          // 0x01
      'https://www.',         // 0x02
      'http://',              // 0x03
      'https://',             // 0x04
      'tel:',                 // 0x05
      'mailto:',              // 0x06
      'ftp://anonymous:anonymous@', // 0x07
      'ftp://ftp.',           // 0x08
      'ftps://',              // 0x09
      'sftp://',              // 0x0A
      'smb://',               // 0x0B
      'nfs://',               // 0x0C
      'ftp://',               // 0x0D
      'dav://',               // 0x0E
      'news:',                // 0x0F
      'telnet://',            // 0x10
      'imap:',                // 0x11
      'rtsp://',              // 0x12
      'urn:',                 // 0x13
      'pop:',                 // 0x14
      'sip:',                 // 0x15
      'sips:',                // 0x16
      'tftp:',                // 0x17
      'btspp://',             // 0x18
      'btl2cap://',           // 0x19
      'btgoep://',            // 0x1A
      'tcpobex://',           // 0x1B
      'irdaobex://',          // 0x1C
      'file://',              // 0x1D
      'urn:epc:id:',          // 0x1E
      'urn:epc:tag:',         // 0x1F
      'urn:epc:pat:',         // 0x20
      'urn:epc:sct:',         // 0x21
      'urn:epc:raw:',         // 0x22
      'urn:epc:',             // 0x23
      'urn:nfc:'              // 0x24
    ];
    
    // 查找匹配的前缀
    let prefixCode = 0;
    let remainingUri = uri;
    
    for (let i = prefixes.length - 1; i > 0; i--) {
      if (uri.startsWith(prefixes[i])) {
        prefixCode = i;
        remainingUri = uri.substring(prefixes[i].length);
        break;
      }
    }
    
    // 构建payload: [前缀代码 | 剩余URI]
    const uriBytes = new TextEncoder().encode(remainingUri);
    const payload = new ArrayBuffer(1 + uriBytes.length);
    const view = new Uint8Array(payload);
    
    view[0] = prefixCode;
    view.set(uriBytes, 1);
    
    return {
      tnf: nfc.Tnf.WELL_KNOWN,
      type: new TextEncoder().encode('U'), // 'U'表示URI类型
      payload: payload
    };
  }

  /**
   * 解析URI记录
   */
  public static parseUriRecord(record: nfc.NdefRecord): string {
    if (record.tnf !== nfc.Tnf.WELL_KNOWN) {
      throw new Error('不是URI记录');
    }
    
    const typeStr = new TextDecoder().decode(record.type);
    if (typeStr !== 'U') {
      throw new Error('不是URI记录');
    }
    
    const prefixes = [
      '', 'http://www.', 'https://www.', 'http://', 'https://',
      'tel:', 'mailto:', 'ftp://anonymous:anonymous@', 'ftp://ftp.',
      'ftps://', 'sftp://', 'smb://', 'nfs://', 'ftp://', 'dav://',
      'news:', 'telnet://', 'imap:', 'rtsp://', 'urn:', 'pop:',
      'sip:', 'sips:', 'tftp:', 'btspp://', 'btl2cap://', 'btgoep://',
      'tcpobex://', 'irdaobex://', 'file://', 'urn:epc:id:',
      'urn:epc:tag:', 'urn:epc:pat:', 'urn:epc:sct:', 'urn:epc:raw:',
      'urn:epc:', 'urn:nfc:'
    ];
    
    const payload = new Uint8Array(record.payload);
    const prefixCode = payload[0];
    const remainingUri = new TextDecoder().decode(payload.slice(1));
    
    return (prefixes[prefixCode] || '') + remainingUri;
  }

  /**
   * 创建MIME类型记录
   */
  public static createMimeRecord(mimeType: string, data: ArrayBuffer): nfc.NdefRecord {
    return {
      tnf: nfc.Tnf.MIME_MEDIA,
      type: new TextEncoder().encode(mimeType),
      payload: data
    };
  }

  /**
   * 创建外部类型记录
   */
  public static createExternalRecord(domain: string, type: string, data: ArrayBuffer): nfc.NdefRecord {
    const externalType = `${domain}:${type}`;
    return {
      tnf: nfc.Tnf.EXTERNAL,
      type: new TextEncoder().encode(externalType),
      payload: data
    };
  }

  /**
   * 创建AAR记录(Android应用启动)
   */
  public static createAarRecord(packageName: string): nfc.NdefRecord {
    return {
      tnf: nfc.Tnf.EXTERNAL,
      type: new TextEncoder().encode('android.com:pkg'),
      payload: new TextEncoder().encode(packageName)
    };
  }
}

/**
 * NFC标签管理器
 * 封装标签发现、读写等功能
 */
export class NfcTagManager {
  private static instance: NfcTagManager;
  
  // 当前检测到的标签
  private currentTag: nfc.TagInfo | null = null;
  
  // 回调函数
  private onTagDiscovered: ((tag: nfc.TagInfo) => void) | null = null;

  private constructor() {
    this.initEventListeners();
  }

  /**
   * 获取单例实例
   */
  public static getInstance(): NfcTagManager {
    if (!NfcTagManager.instance) {
      NfcTagManager.instance = new NfcTagManager();
    }
    return NfcTagManager.instance;
  }

  /**
   * 初始化事件监听
   */
  private initEventListeners(): void {
    // 监听标签发现
    nfc.on('tagDiscovered', (tag: nfc.TagInfo) => {
      console.info(`[NFC] 发现标签: ${tag.technology}`);
      this.currentTag = tag;
      
      if (this.onTagDiscovered) {
        this.onTagDiscovered(tag);
      }
    });
  }

  /**
   * 检查NFC是否开启
   */
  public async isNfcEnabled(): Promise<boolean> {
    try {
      const isOpen = nfc.isNfcOpen();
      return isOpen;
    } catch (error) {
      console.error('[NFC] 检查状态失败:', error);
      return false;
    }
  }

  /**
   * 开启NFC
   */
  public async enableNfc(): Promise<boolean> {
    try {
      await nfc.openNfc();
      console.info('[NFC] 已开启');
      return true;
    } catch (error) {
      const err = error as BusinessError;
      console.error(`[NFC] 开启失败: ${err.message}`);
      return false;
    }
  }

  /**
   * 关闭NFC
   */
  public async disableNfc(): Promise<boolean> {
    try {
      await nfc.closeNfc();
      console.info('[NFC] 已关闭');
      return true;
    } catch (error) {
      const err = error as BusinessError;
      console.error(`[NFC] 关闭失败: ${err.message}`);
      return false;
    }
  }

  /**
   * 读取NDEF消息
   */
  public async readNdefMessage(): Promise<nfc.NdefMessage | null> {
    if (!this.currentTag) {
      console.error('[NFC] 未检测到标签');
      return null;
    }

    try {
      // 获取NDEF标签技术
      const ndefTag = nfc.getNdefTag(this.currentTag);
      if (!ndefTag) {
        console.error('[NFC] 标签不支持NDEF');
        return null;
      }

      // 读取NDEF消息
      const message = await ndefTag.readNdefMessage();
      console.info(`[NFC] 读取成功,${message.records.length} 条记录`);
      return message;
    } catch (error) {
      console.error('[NFC] 读取失败:', error);
      return null;
    }
  }

  /**
   * 写入NDEF消息
   */
  public async writeNdefMessage(message: nfc.NdefMessage): Promise<boolean> {
    if (!this.currentTag) {
      console.error('[NFC] 未检测到标签');
      return false;
    }

    try {
      // 获取NDEF标签技术
      const ndefTag = nfc.getNdefTag(this.currentTag);
      if (!ndefTag) {
        console.error('[NFC] 标签不支持NDEF');
        return false;
      }

      // 检查是否可写
      const isWritable = await ndefTag.isNdefWritable();
      if (!isWritable) {
        console.error('[NFC] 标签不可写');
        return false;
      }

      // 写入NDEF消息
      await ndefTag.writeNdefMessage(message);
      console.info('[NFC] 写入成功');
      return true;
    } catch (error) {
      console.error('[NFC] 写入失败:', error);
      return false;
    }
  }

  /**
   * 格式化标签(将标签格式化为NDEF)
   */
  public async formatTag(): Promise<boolean> {
    if (!this.currentTag) {
      console.error('[NFC] 未检测到标签');
      return false;
    }

    try {
      const ndefTag = nfc.getNdefTag(this.currentTag);
      if (!ndefTag) {
        return false;
      }

      await ndefTag.formatNdef();
      console.info('[NFC] 格式化成功');
      return true;
    } catch (error) {
      console.error('[NFC] 格式化失败:', error);
      return false;
    }
  }

  /**
   * 获取标签信息
   */
  public getTagInfo(): nfc.TagInfo | null {
    return this.currentTag;
  }

  /**
   * 解析NDEF消息内容
   */
  public parseNdefMessage(message: nfc.NdefMessage): Array<{
    type: string;
    content: any;
  }> {
    const results: Array<{ type: string; content: any }> = [];

    message.records.forEach((record) => {
      try {
        if (record.tnf === nfc.Tnf.WELL_KNOWN) {
          const typeStr = new TextDecoder().decode(record.type);
          
          if (typeStr === 'T') {
            // 文本记录
            results.push({
              type: 'text',
              content: NdefHelper.parseTextRecord(record)
            });
          } else if (typeStr === 'U') {
            // URI记录
            results.push({
              type: 'uri',
              content: NdefHelper.parseUriRecord(record)
            });
          }
        } else if (record.tnf === nfc.Tnf.MIME_MEDIA) {
          // MIME类型记录
          const mimeType = new TextDecoder().decode(record.type);
          results.push({
            type: 'mime',
            content: {
              mimeType: mimeType,
              data: record.payload
            }
          });
        } else if (record.tnf === nfc.Tnf.EXTERNAL) {
          // 外部类型记录
          const externalType = new TextDecoder().decode(record.type);
          results.push({
            type: 'external',
            content: {
              externalType: externalType,
              data: record.payload
            }
          });
        }
      } catch (error) {
        console.warn('[NFC] 解析记录失败:', error);
      }
    });

    return results;
  }

  /**
   * 设置标签发现回调
   */
  public setOnTagDiscovered(callback: (tag: nfc.TagInfo) => void): void {
    this.onTagDiscovered = callback;
  }

  /**
   * 移除监听
   */
  public removeAllListeners(): void {
    nfc.off('tagDiscovered');
    this.currentTag = null;
    console.info('[NFC] 已移除监听');
  }
}

3.2 NFC标签读写UI实现

import { NfcTagManager, NdefHelper } from './NfcTagManager';
import nfc from '@ohos.nfc';

@Entry
@Component
struct NfcReadWritePage {
  private nfcManager: NfcTagManager = NfcTagManager.getInstance();
  
  @State isNfcEnabled: boolean = false;
  @State hasTag: boolean = false;
  @State tagInfo: string = '';
  @State ndefRecords: Array<{ type: string; content: any }> = [];
  @State writeText: string = '';
  @State writeUri: string = '';
  @State isReading: boolean = false;
  @State isWriting: boolean = false;

  aboutToAppear(): void {
    this.checkNfcState();
    this.registerCallbacks();
  }

  aboutToDisappear(): void {
    this.nfcManager.removeAllListeners();
  }

  /**
   * 检查NFC状态
   */
  async checkNfcState(): Promise<void> {
    this.isNfcEnabled = await this.nfcManager.isNfcEnabled();
  }

  /**
   * 注册回调
   */
  registerCallbacks(): void {
    this.nfcManager.setOnTagDiscovered((tag: nfc.TagInfo) => {
      this.hasTag = true;
      this.tagInfo = `技术: ${tag.technology}\nID: ${this.formatTagId(tag.uid)}`;
      
      // 自动读取
      this.readTag();
    });
  }

  /**
   * 格式化标签ID
   */
  formatTagId(uid: ArrayBuffer): string {
    if (!uid) return 'Unknown';
    const bytes = new Uint8Array(uid);
    return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join(':').toUpperCase();
  }

  /**
   * 读取标签
   */
  async readTag(): Promise<void> {
    this.isReading = true;
    
    const message = await this.nfcManager.readNdefMessage();
    if (message) {
      this.ndefRecords = this.nfcManager.parseNdefMessage(message);
    } else {
      this.ndefRecords = [];
    }
    
    this.isReading = false;
  }

  /**
   * 写入文本
   */
  async writeTextToTag(): Promise<void> {
    if (!this.writeText) return;
    
    this.isWriting = true;
    
    const record = NdefHelper.createTextRecord(this.writeText, 'zh');
    const message: nfc.NdefMessage = {
      records: [record]
    };
    
    const success = await this.nfcManager.writeNdefMessage(message);
    if (success) {
      this.writeText = '';
      await this.readTag();
    }
    
    this.isWriting = false;
  }

  /**
   * 写入URI
   */
  async writeUriToTag(): Promise<void> {
    if (!this.writeUri) return;
    
    this.isWriting = true;
    
    const record = NdefHelper.createUriRecord(this.writeUri);
    const message: nfc.NdefMessage = {
      records: [record]
    };
    
    const success = await this.nfcManager.writeNdefMessage(message);
    if (success) {
      this.writeUri = '';
      await this.readTag();
    }
    
    this.isWriting = false;
  }

  /**
   * 清空标签
   */
  async clearTag(): Promise<void> {
    this.isWriting = true;
    
    const message: nfc.NdefMessage = {
      records: []
    };
    
    await this.nfcManager.writeNdefMessage(message);
    await this.readTag();
    
    this.isWriting = false;
  }

  build() {
    Column() {
      // 标题栏
      Row() {
        Text('NFC标签读写')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FFFFFF')

        Blank()

        Toggle({ type: ToggleType.Switch, isOn: this.isNfcEnabled })
          .onChange((isOn: boolean) => {
            if (isOn) {
              this.nfcManager.enableNfc();
            } else {
              this.nfcManager.disableNfc();
            }
            this.isNfcEnabled = isOn;
          })
      }
      .width('100%')
      .padding(20)
      .backgroundColor('#1A1A2E')

      if (this.isNfcEnabled) {
        // 标签信息
        if (this.hasTag) {
          Column() {
            Text('标签信息')
              .fontSize(16)
              .fontColor('#FFFFFF')
              .width('100%')
              .margin({ bottom: 10 })

            Text(this.tagInfo)
              .fontSize(14)
              .fontColor('#AAAAAA')
              .width('100%')
          }
          .width('90%')
          .padding(15)
          .backgroundColor('#16213E')
          .borderRadius(12)
          .margin({ top: 20 })

          // 读取结果
          Column() {
            Row() {
              Text('NDEF记录')
                .fontSize(16)
                .fontColor('#FFFFFF')

              Blank()

              Button('刷新')
                .fontSize(12)
                .height(30)
                .backgroundColor('#4A90E2')
                .enabled(!this.isReading)
                .onClick(() => {
                  this.readTag();
                })
            }
            .width('100%')
            .margin({ bottom: 10 })

            if (this.isReading) {
              Row() {
                LoadingProgress()
                  .width(30)
                  .height(30)

                Text('读取中...')
                  .fontSize(14)
                  .fontColor('#AAAAAA')
                  .margin({ left: 10 })
              }
              .width('100%')
              .justifyContent(FlexAlign.Center)
              .padding(20)
            } else if (this.ndefRecords.length > 0) {
              ForEach(this.ndefRecords, (record: { type: string; content: any }, index: number) => {
                Row() {
                  Text(`[${record.type}]`)
                    .fontSize(12)
                    .fontColor('#F5A623')

                  if (record.type === 'text' || record.type === 'uri') {
                    Text(record.content)
                      .fontSize(14)
                      .fontColor('#FFFFFF')
                      .margin({ left: 10 })
                      .layoutWeight(1)
                  } else {
                    Text(JSON.stringify(record.content))
                      .fontSize(12)
                      .fontColor('#AAAAAA')
                      .margin({ left: 10 })
                      .layoutWeight(1)
                  }
                }
                .width('100%')
                .padding(10)
                .backgroundColor('#1A1A2E')
                .borderRadius(8)
                .margin({ bottom: 8 })
              })
            } else {
              Text('无NDEF记录')
                .fontSize(14)
                .fontColor('#AAAAAA')
                .width('100%')
                .textAlign(TextAlign.Center)
                .padding(20)
            }
          }
          .width('90%')
          .padding(15)
          .backgroundColor('#16213E')
          .borderRadius(12)
          .margin({ top: 20 })

          // 写入区域
          Column() {
            Text('写入数据')
              .fontSize(16)
              .fontColor('#FFFFFF')
              .width('100%')
              .margin({ bottom: 15 })

            // 写入文本
            Row() {
              TextInput({ text: $$this.writeText, placeholder: '输入文本' })
                .width('70%')
                .height(40)
                .backgroundColor('#1A1A2E')
                .fontColor('#FFFFFF')

              Button('写入')
                .width('25%')
                .height(40)
                .backgroundColor('#4CAF50')
                .enabled(!this.isWriting && this.writeText.length > 0)
                .onClick(() => {
                  this.writeTextToTag();
                })
            }
            .width('100%')
            .justifyContent(FlexAlign.SpaceBetween)
            .margin({ bottom: 10 })

            // 写入URI
            Row() {
              TextInput({ text: $$this.writeUri, placeholder: '输入网址' })
                .width('70%')
                .height(40)
                .backgroundColor('#1A1A2E')
                .fontColor('#FFFFFF')

              Button('写入')
                .width('25%')
                .height(40)
                .backgroundColor('#4A90E2')
                .enabled(!this.isWriting && this.writeUri.length > 0)
                .onClick(() => {
                  this.writeUriToTag();
                })
            }
            .width('100%')
            .justifyContent(FlexAlign.SpaceBetween)
            .margin({ bottom: 15 })

            // 清空按钮
            Button('清空标签')
              .width('100%')
              .height(40)
              .backgroundColor('#FF5722')
              .enabled(!this.isWriting)
              .onClick(() => {
                this.clearTag();
              })
          }
          .width('90%')
          .padding(15)
          .backgroundColor('#16213E')
          .borderRadius(12)
          .margin({ top: 20 })
        } else {
          // 等待标签
          Column() {
            Text('请将NFC标签靠近设备')
              .fontSize(18)
              .fontColor('#AAAAAA')

            LoadingProgress()
              .width(50)
              .height(50)
              .margin({ top: 20 })
          }
          .margin({ top: 100 })
        }
      } else {
        // NFC未开启
        Column() {
          Text('NFC未开启')
            .fontSize(18)
            .fontColor('#AAAAAA')

          Text('请开启NFC以读写标签')
            .fontSize(14)
            .fontColor('#666666')
            .margin({ top: 10 })
        }
        .margin({ top: 100 })
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#0F0F1A')
  }
}

3.3 智能海报示例

智能海报(Smart Poster)是一种复合NDEF记录,可以同时包含URI、文本、图标等信息:

import { NdefHelper } from './NdefHelper';
import nfc from '@ohos.nfc';

/**
 * 智能海报构建器
 */
export class SmartPosterBuilder {
  private records: nfc.NdefRecord[] = [];
  private mainUri: string = '';

  /**
   * 设置主URI
   */
  public setUri(uri: string): SmartPosterBuilder {
    this.mainUri = uri;
    this.records.push(NdefHelper.createUriRecord(uri));
    return this;
  }

  /**
   * 添加标题
   */
  public addTitle(title: string, language: string = 'en'): SmartPosterBuilder {
    this.records.push(NdefHelper.createTextRecord(title, language));
    return this;
  }

  /**
   * 添加图标(MIME类型)
   */
  public addIcon(imageData: ArrayBuffer, mimeType: string = 'image/png'): SmartPosterBuilder {
    this.records.push(NdefHelper.createMimeRecord(mimeType, imageData));
    return this;
  }

  /**
   * 添加动作
   * 0: 执行动作(如打开应用)
   * 1: 保存动作(如保存联系人)
   * 2: 编辑动作
   */
  public addAction(action: number): SmartPosterBuilder {
    const payload = new ArrayBuffer(1);
    new Uint8Array(payload)[0] = action;
    
    this.records.push({
      tnf: nfc.Tnf.WELL_KNOWN,
      type: new TextEncoder().encode('act'),
      payload: payload
    });
    return this;
  }

  /**
   * 构建智能海报NDEF消息
   */
  public build(): nfc.NdefMessage {
    // 智能海报是一个复合记录,包含多个子记录
    const spPayload = this.encodeRecords(this.records);
    
    const spRecord: nfc.NdefRecord = {
      tnf: nfc.Tnf.WELL_KNOWN,
      type: new TextEncoder().encode('Sp'), // 'Sp'表示Smart Poster
      payload: spPayload
    };

    return {
      records: [spRecord]
    };
  }

  /**
   * 编码多个记录为一个payload
   */
  private encodeRecords(records: nfc.NdefRecord[]): ArrayBuffer {
    // 简化实现,实际需要按照NDEF规范编码
    // 这里只是示意
    let totalLength = 0;
    records.forEach(r => {
      totalLength += this.calculateRecordSize(r);
    });

    const buffer = new ArrayBuffer(totalLength);
    // 实际编码逻辑...
    return buffer;
  }

  private calculateRecordSize(record: nfc.NdefRecord): number {
    return 3 + record.type.byteLength + 
           (record.id ? record.id.byteLength : 0) + 
           record.payload.byteLength;
  }
}

// 使用示例
async function createSmartPoster() {
  const message = new SmartPosterBuilder()
    .setUri('https://www.example.com/product/123')
    .addTitle('特价商品', 'zh')
    .addTitle('Special Offer', 'en')
    .addAction(0) // 打开网页
    .build();

  await nfcManager.writeNdefMessage(message);
}

四、踩坑与注意事项

4.1 权限配置

// module.json5
{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.NFC",
        "reason": "使用NFC功能"
      },
      {
        "name": "ohos.permission.NFC_TAG",
        "reason": "读写NFC标签"
      }
    ]
  }
}

4.2 前台调度

坑点:NFC检测需要应用在前台。

/**
 * 注册前台NFC调度
 */
async function registerForegroundDispatch(): Promise<void> {
  // 在Ability的onForeground中调用
  try {
    await nfc.enableForegroundDispatch();
    console.info('[NFC] 前台调度已启用');
  } catch (error) {
    console.error('[NFC] 启用前台调度失败:', error);
  }
}

/**
 * 取消前台NFC调度
 */
async function disableForegroundDispatch(): Promise<void> {
  // 在Ability的onBackground中调用
  try {
    await nfc.disableForegroundDispatch();
    console.info('[NFC] 前台调度已禁用');
  } catch (error) {
    console.error('[NFC] 禁用前台调度失败:', error);
  }
}

4.3 标签类型判断

坑点:不同标签支持的技术不同。

/**
 * 检查标签能力
 */
async function checkTagCapabilities(tag: nfc.TagInfo): Promise<{
  canRead: boolean;
  canWrite: boolean;
  canFormat: boolean;
}> {
  const result = {
    canRead: false,
    canWrite: false,
    canFormat: false
  };

  // 检查是否支持NDEF
  const ndefTag = nfc.getNdefTag(tag);
  if (ndefTag) {
    result.canRead = true;
    
    try {
      const isWritable = await ndefTag.isNdefWritable();
      result.canWrite = isWritable;
    } catch (error) {
      result.canWrite = false;
    }
  }

  // 检查是否可格式化
  const ndefFormatable = nfc.getNdefFormatableTag(tag);
  result.canFormat = !!ndefFormatable;

  return result;
}

4.4 写入容量检查

坑点:标签容量有限,写入前需要检查。

/**
 * 检查消息大小是否适合标签
 */
async function checkMessageSize(message: nfc.NdefMessage): Promise<boolean> {
  const tag = nfcManager.getTagInfo();
  if (!tag) return false;

  const ndefTag = nfc.getNdefTag(tag);
  if (!ndefTag) return false;

  try {
    const maxSize = await ndefTag.getMaxNdefMessageSize();
    const messageSize = calculateMessageSize(message);

    if (messageSize > maxSize) {
      console.error(`[NFC] 消息大小 ${messageSize} 超过标签容量 ${maxSize}`);
      return false;
    }

    return true;
  } catch (error) {
    return false;
  }
}

/**
 * 计算NDEF消息大小
 */
function calculateMessageSize(message: nfc.NdefMessage): number {
  let size = 0;
  
  message.records.forEach((record) => {
    // NDEF记录头 + 类型 + ID + 负载
    size += 3; // 头部
    size += record.type.byteLength;
    if (record.id) size += record.id.byteLength;
    size += record.payload.byteLength;
  });
  
  return size;
}

4.5 数据编码问题

坑点:文本编码需要指定语言和编码方式。

/**
 * 正确的文本记录创建
 */
function createTextRecordProper(text: string, language: string = 'zh'): nfc.NdefRecord {
  const langBytes = new TextEncoder().encode(language);
  const textBytes = new TextEncoder().encode(text);
  
  // Payload格式: [状态字节 | 语言代码 | 文本]
  // 状态字节: bit7=编码(0=UTF-8,1=UTF-16), bit6=保留, bit5-0=语言代码长度
  const statusByte = langBytes.length & 0x3F; // UTF-8编码
  
  const payload = new ArrayBuffer(1 + langBytes.length + textBytes.length);
  const view = new Uint8Array(payload);
  
  view[0] = statusByte;
  view.set(langBytes, 1);
  view.set(textBytes, 1 + langBytes.length);
  
  return {
    tnf: nfc.Tnf.WELL_KNOWN,
    type: new TextEncoder().encode('T'),
    payload: payload
  };
}

五、HarmonyOS 6适配

5.1 API变更

API HarmonyOS 5 HarmonyOS 6 说明
readNdefMessage() 同步返回 异步Promise 改为异步
writeNdefMessage() 基础功能 支持覆盖写入 新增覆盖选项
新增 - getTagTechnologyList() 获取支持的技术列表

适配代码

/**
 * HarmonyOS 6 NFC适配
 */
async function readNdefAdaptive(): Promise<nfc.NdefMessage | null> {
  const apiVersion = getApiVersion();
  const tag = nfcManager.getTagInfo();
  
  if (!tag) return null;

  try {
    const ndefTag = nfc.getNdefTag(tag);
    if (!ndefTag) return null;

    if (apiVersion >= 6) {
      // HarmonyOS 6
      return await ndefTag.readNdefMessage();
    } else {
      // HarmonyOS 5
      return ndefTag.readNdefMessage();
    }
  } catch (error) {
    console.error('[NFC] 读取失败:', error);
    return null;
  }
}

5.2 新增功能

HarmonyOS 6增强了NFC能力:

// 1. 获取标签支持的所有技术
const technologies = await nfc.getTagTechnologyList(tag);
console.info(`支持的技术: ${technologies.join(', ')}`);

// 2. NDEF消息缓存
const cachedMessage = await ndefTag.getCachedNdefMessage();
if (cachedMessage) {
  console.info('[NFC] 使用缓存消息');
}

// 3. 标签连接保持
await nfc.setTagConnectionTimeout(5000); // 5秒超时

// 4. 批量写入
await ndefTag.writeNdefMessageBatch([
  message1,
  message2
]);

5.3 性能优化

// 1. 快速读取模式
await nfc.setFastReadMode(true);

// 2. 预解析NDEF
const preParsed = await nfc.preParseNdefMessage(tag);

// 3. 连接复用
const connection = await nfc.establishTagConnection(tag);
// 多次操作...
await connection.close();

六、总结

NFC标签读写是物联网应用的重要入口,掌握NDEF消息处理是NFC开发的核心技能。本文全面讲解了鸿蒙系统中NFC开发的核心要点:

核心要点回顾

  1. NDEF格式:理解NDEF消息和记录的结构
  2. 记录类型:掌握文本、URI、MIME等常见记录的创建和解析
  3. 标签技术:根据标签类型选择合适的读写方式
  4. 容量管理:写入前检查消息大小是否适合标签
  5. 编码处理:正确处理文本编码和URI缩写

最佳实践建议

  • 封装NDEF工具类,简化记录创建和解析
  • 实现完善的错误处理和用户提示
  • 做好标签类型兼容性处理
  • 使用前台调度确保及时检测

下一步学习

  • NFC卡模拟(下一篇文章)
  • NFC P2P通信
  • 多标签同时处理
  • NFC安全机制

NFC技术简单易用,但细节不少。希望本文能帮助你掌握NFC标签读写的核心技能,让你的应用具备"碰一碰"的能力!

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。