
一、 引言:近場通信在 HarmonyOS 生態(tài)中的戰(zhàn)略地位
在萬物互聯(lián)的時代,設(shè)備間的無縫協(xié)同已成為用戶體驗的核心。HarmonyOS 作為面向全場景的分布式操作系統(tǒng),其內(nèi)置的 Connectivity Kit(短距通信服務(wù))為開發(fā)者提供了包括藍(lán)牙、WLAN 和 NFC 在內(nèi)的豐富近場通信能力。其中,NFC(近場通信)憑借其超低功耗、高安全性和觸碰即連的特性,在設(shè)備發(fā)現(xiàn)、身份認(rèn)證和小數(shù)據(jù)量快速交換等場景中扮演著不可替代的角色。
雖然 NFC 的理論傳輸速率(最高 424 Kbps)遠(yuǎn)低于 WLAN Direct 等技術(shù),但其在建立連接的瞬間完成性上具有巨大優(yōu)勢。一個典型的應(yīng)用場景是:用戶只需將兩臺 HarmonyOS 設(shè)備輕輕觸碰,即可瞬間啟動一個高速的 WLAN Direct 通道用于大文件傳輸,而 NFC 則完美地承擔(dān)了 “握手” 和 “通道建立” 的任務(wù)。本文將聚焦于純 NFC P2P 模式下的文件傳輸實現(xiàn),作為理解 HarmonyOS 近場能力的基礎(chǔ),并為更復(fù)雜的混合傳輸方案奠定技術(shù)根基。

二、 技術(shù)基石:HarmonyOS NFC P2P 通信詳解
2.1 NFC P2P 工作模式概述
NFC 支持三種主要工作模式:讀卡器模式(Reader/Writer)、卡模擬模式(Card Emulation)和點對點模式(Peer-to-Peer, P2P)。對于設(shè)備間文件傳輸,我們關(guān)注的是 P2P 模式。在此模式下,兩臺設(shè)備可以像兩個對等的節(jié)點一樣,直接交換數(shù)據(jù)。
HarmonyOS 通過 @ohos.nfc.p2p 模塊提供了對 NFC P2P 的支持。開發(fā)者可以使用 sendNdefMessage 和 receiveNdefMessage 等 API 來發(fā)送和接收 NDEF(NFC Data Exchange Format)格式的消息。NDEF 是一種輕量級的二進(jìn)制消息格式,非常適合在 NFC 設(shè)備間交換文本、URI 或小型二進(jìn)制數(shù)據(jù)。
2.2 權(quán)限與配置
在動手編碼前,必須在 module.json5 文件中聲明必要的權(quán)限和配置。
代碼示例 1:module.json5 配置
{
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": [
"phone",
"tablet"
],
"requestPermissions": [
{
"name": "ohos.permission.NFC_TAG",
"reason": "$string:permission_nfc_tag_reason",
"usedScene": {
"when": "$string:permission_nfc_tag_when",
"description": "$string:permission_nfc_tag_desc"
}
},
{
"name": "ohos.permission.NFC_P2P",
"reason": "$string:permission_nfc_p2p_reason",
"usedScene": {
"when": "$string:permission_nfc_p2p_when",
"description": "$string:permission_nfc_p2p_desc"
}
},
{
"name": "ohos.permission.READ_MEDIA",
"reason": "$string:permission_read_media_reason",
"usedScene": {
"when": "$string:permission_read_media_when",
"description": "$string:permission_read_media_desc"
}
}
],
//... 其他配置
}
}
以上配置聲明了 NFC 標(biāo)簽讀寫、NFC P2P 通信以及讀取媒體文件的權(quán)限。這是應(yīng)用能夠訪問 NFC 硬件和文件系統(tǒng)的前提。
2.3 初始化 NFC P2P 監(jiān)聽器
應(yīng)用啟動后,需要初始化 NFC P2P 監(jiān)聽器,以便在設(shè)備觸碰時接收數(shù)據(jù)。
代碼示例 2:初始化 P2P 監(jiān)聽器
// NFCManager.ets
import nfcP2p from '@ohos.nfc.p2p';
import { BusinessError } from '@ohos.base';
class NFCManager {
private static instance: NFCManager;
private isInitialized: boolean = false;
private constructor() {}
public static getInstance(): NFCManager {
if (!NFCManager.instance) {
NFCManager.instance = new NFCManager();
}
return NFCManager.instance;
}
public initP2PListener(onReceive: (data: ArrayBuffer) => void): void {
if (this.isInitialized) return;
try {
nfcP2p.on('receiveNdefMessage', (event) => {
//event.message.records 包含接收到的 NDEF 記錄
if (event.message.records && event.message.records.length > 0) {
const firstRecord = event.message.records[0];
if (firstRecord.tnf === nfcP2p.TNF_WELL_KNOWN &&
firstRecord.type && firstRecord.type.length > 0) {
// 假設(shè)我們約定使用自定義的 TNF 和 Type 來傳輸文件數(shù)據(jù)
onReceive(firstRecord.payload);
}
}
});
this.isInitialized = true;
console.log('NFC P2P listener initialized successfully.');
} catch (error) {
let err = error as BusinessError;
console.error(`Failed to init NFC P2P listener. Code: ${err.code}, Message: ${err.message}`);
}
}
public sendFileData(data: ArrayBuffer): void {
try {
const ndefRecord = {
tnf: nfcP2p.TNF_UNCHANGED, // 或者使用自定義 TNF
type: new Uint8Array([0x68, 0x61, 0x72, 0x6d, 0x6f, 0x6e, 0x79]), // "harmony" as type
id: new Uint8Array([]),
payload: data
};
const ndefMessage = {
records: [ndefRecord]
};
nfcP2p.sendNdefMessage(ndefMessage);
console.log('File data sent via NFC P2P.');
} catch (error) {
let err = error as BusinessError;
console.error(`Failed to send file data. Code: ${err.code}, Message: ${err.message}`);
}
}
}
export default NFCManager;
這段代碼封裝了一個 NFCManager 單例,負(fù)責(zé)初始化 P2P 監(jiān)聽器和發(fā)送文件數(shù)據(jù)。它使用了 NDEF 消息格式,并約定了一種自定義的 Type 字段來標(biāo)識我們的文件傳輸協(xié)議。
三、 文件系統(tǒng)操作:讀取與寫入
NFC 適合傳輸小文件(如文本、小圖片)。對于大文件,通常的做法是通過 NFC 傳遞一個文件 URI 或會話密鑰,然后在后臺建立高速通道進(jìn)行傳輸。但為了演示完整性,我們先展示如何通過 NFC 傳輸小文件的完整二進(jìn)制數(shù)據(jù)。
3.1 讀取本地文件
使用 @ohos.file.fs 模塊讀取用戶選擇的文件。
代碼示例 3:讀取文件為 ArrayBuffer
// FileManager.ets
import fs from '@ohos.file.fs';
import { BusinessError } from '@ohos.base';
class FileManager {
public static async readFileAsArrayBuffer(filePath: string): Promise
try {
const file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
const stat = fs.statSync(filePath);
const buffer = new ArrayBuffer(stat.size);
const dataView = new DataView(buffer);
fs.readSync(file.fd, buffer);
fs.closeSync(file);
return buffer;
} catch (error) {
let err = error as BusinessError;
console.error(`Failed to read file: ${filePath}. Code: ${err.code}, Message: ${err.message}`);
return null;
}
}
public static async writeFileFromArrayBuffer(filePath: string, data: ArrayBuffer): Promise
try {
const file = fs.openSync(filePath, fs.OpenMode.CREATE | fs.OpenMode.WRITE_ONLY);
fs.writeSync(file.fd, data);
fs.closeSync(file);
console.log(`File written successfully: ${filePath}`);
return true;
} catch (error) {
let err = error as BusinessError;
console.error(`Failed to write file: ${filePath}. Code: ${err.code}, Message: ${err.message}`);
return false;
}
}
}
export default FileManager;
FileManager 類提供了同步讀寫文件的方法,將文件內(nèi)容轉(zhuǎn)換為 ArrayBuffer,這是與 NFC API 交互的理想數(shù)據(jù)格式。
3.2 UIAbility 生命周期與文件接收
當(dāng)應(yīng)用通過 NFC 接收到文件數(shù)據(jù)時,需要在一個合適的生命周期回調(diào)中處理它。通常,我們會在 UIAbility 的 onCreate 或 onNewWant 中處理來自其他應(yīng)用的意圖。
代碼示例 4:在 UIAbility 中處理接收的文件
// EntryAbility/EntryAbility.ts
import UIAbility from '@ohos.app.ability.UIAbility';
import NFCManager from '../common/NFCManager';
import FileManager from '../common/FileManager';
import { BusinessError } from '@ohos.base';
export default class EntryAbility extends UIAbility {
onCreate(want, launchParam) {
// 初始化 NFC 監(jiān)聽器
NFCManager.getInstance().initP2PListener((receivedData: ArrayBuffer) => {
this.handleReceivedFile(receivedData);
});
}
private async handleReceivedFile(data: ArrayBuffer): Promise
// 1. 生成一個唯一的文件名
const timestamp = new Date().getTime();
const fileName = `nfc_received_file_${timestamp}.bin`;
// 2. 確定保存路徑(例如應(yīng)用的緩存目錄)
const cacheDir = this.context.cacheDir;
const filePath = `${cacheDir}/${fileName}`;
// 3. 寫入文件
const success = await FileManager.writeFileFromArrayBuffer(filePath, data);
if (success) {
// 4. 通知 UI 或用戶文件已接收
// 這里可以通過 EventHub 或狀態(tài)管理通知頁面
console.log(`File received and saved t ${filePath}`);
}
}
onNewWant(want, launchParam) {
// 處理應(yīng)用已在運(yùn)行時,通過意圖再次啟動的情況
// 可用于處理通過分享菜單啟動的場景
}
}
在 EntryAbility 的 onCreate 中初始化 NFC 監(jiān)聽器,并在接收到數(shù)據(jù)時,調(diào)用 handleReceivedFile 方法將其保存到應(yīng)用的緩存目錄中。
四、 構(gòu)建完整用戶體驗:UI 與交互設(shè)計
4.1 發(fā)送端 UI
發(fā)送端需要一個界面讓用戶選擇文件,并提供一個醒目的 “觸碰發(fā)送” 按鈕。
// pages/SendPage.ets
import { router } from '@kit.ArkTS';
import NFCManager from '../common/NFCManager';
import FileManager from '../common/FileManager';
import picker from '@ohos.file.picker';
@Entry
@Component
struct SendPage {
@State selectedFile: string = '';
@State isSending: boolean = false;
build() {
Column() {
Text ('NFC 文件發(fā)送 ')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin(20)
if (this.selectedFile) {
Text (`已選擇: ${this.selectedFile}`)
.fontSize(16)
.margin({ bottom: 20 })
}
Button (' 選擇文件 ')
.onClick(async () => {
const result = await picker.pickFile();
if (result && result.length > 0) {
this.selectedFile = result[0].uri;
}
})
.margin({ bottom: 20 })
Button (this.isSending ? ' 發(fā)送中...' : ' 觸碰另一臺設(shè)備以發(fā)送 ')
.enabled(this.selectedFile !== '' && !this.isSending)
.onClick(async () => {
this.isSending = true;
const buffer = await FileManager.readFileAsArrayBuffer(this.selectedFile);
if (buffer) {
NFCManager.getInstance().sendFileData(buffer);
// 可以在此處添加一個短暫的提示,告知用戶觸碰設(shè)備
}
this.isSending = false;
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
4.2 接收端 UI
接收端應(yīng)有一個常駐的提示,告知用戶可以接收文件,并在文件接收成功后給出明確反饋。
// pages/ReceivePage.ets
@Entry
@Component
struct ReceivePage {
@State receivedFiles: Array
private eventHub = getContext(this).eventHub;
aboutToAppear() {
// 訂閱來自 Ability 的文件接收事件
this.eventHub.on('fileReceived', (filePath: string) => {
this.receivedFiles = [...this.receivedFiles, filePath];
});
}
aboutToDisappear() {
this.eventHub.off('fileReceived');
}
build() {
Column() {
Text ('NFC 文件接收 ')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin(20)
Text (' 請將發(fā)送設(shè)備靠近本機(jī)背面 ')
.fontSize(16)
.fontColor('#666')
.margin({ bottom: 30 })
if (this.receivedFiles.length > 0) {
List() {
ForEach(this.receivedFiles, (filePath) => {
ListItem() {
Row() {
Text(filePath.split('/').pop() || 'Unknown')
Button (' 打開 ')
.onClick(() => {
// 實現(xiàn)打開文件的邏輯
})
.margin({ left: 20 })
}
}
}, item => item)
}
} else {
Text (' 暫無接收到的文件 ')
.fontColor('#999')
}
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
五、 性能考量與優(yōu)化策略
5.1 NFC 傳輸?shù)木窒扌耘c應(yīng)對
NFC 的帶寬限制了其只能用于傳輸小文件(通常建議小于 1KB)。對于大文件,應(yīng)采用混合傳輸模式:
NFC 握手:通過 NFC 交換設(shè)備信息、會話密鑰和文件元數(shù)據(jù)(如文件名、大小、校驗和)。
WLAN Direct 傳輸:利用 @ohos.wifi.p2p 建立高速通道,傳輸文件主體。
結(jié)果驗證:傳輸完成后,再次通過 NFC 或 WLAN 通道驗證文件完整性。
5.2 使用 DevEco Profiler 進(jìn)行性能分析
在開發(fā)過程中,應(yīng)使用 DevEco Profiler 工具監(jiān)控應(yīng)用性能。
CPU Profiler:分析文件讀寫和 NFC 數(shù)據(jù)處理的 CPU 占用,確保不會阻塞主線程。
Memory Profiler:監(jiān)控 ArrayBuffer 的內(nèi)存分配,避免因加載大文件導(dǎo)致內(nèi)存溢出。
Frame Profiler:確保 UI 在文件傳輸過程中依然保持流暢,無卡頓。
圖表 1:混合傳輸模式流程圖
該流程圖清晰地展示了如何結(jié)合 NFC 和 WLAN P2P 的優(yōu)勢,實現(xiàn)高效、安全的大文件傳輸。
表格 1:HarmonyOS 近場通信技術(shù)對比

六、 總結(jié)與展望
6.1 核心要點回顧
本文詳細(xì)闡述了在 HarmonyOS Next 中利用 NFC P2P 能力實現(xiàn)跨設(shè)備文件傳輸?shù)娜^程:
權(quán)限與配置:正確聲明 NFC 和文件系統(tǒng)權(quán)限是功能實現(xiàn)的前提。
NFC P2P 通信:通過 @ohos.nfc.p2p 模塊的 API,實現(xiàn)了設(shè)備間的數(shù)據(jù)收發(fā)。
文件系統(tǒng)操作:使用 @ohos.file.fs 模塊完成了文件的讀取與寫入。
UI 與交互:設(shè)計了符合用戶直覺的發(fā)送和接收界面。
性能與擴(kuò)展:分析了 NFC 的局限性,并提出了混合傳輸?shù)膬?yōu)化方案。
6.2 未來展望
隨著 HarmonyOS 生態(tài)的不斷成熟,近場通信能力將與分布式軟總線、元服務(wù)等特性深度融合。未來的文件傳輸體驗可能會更加智能和無縫,例如:
智能路由:系統(tǒng)根據(jù)文件大小、網(wǎng)絡(luò)環(huán)境和設(shè)備狀態(tài),自動選擇最優(yōu)的傳輸通道(NFC, WLAN, 藍(lán)牙)。
服務(wù)化傳輸:將文件傳輸能力封裝為元服務(wù),任何應(yīng)用都可以通過標(biāo)準(zhǔn)接口調(diào)用,實現(xiàn) “一次開發(fā),處處可用”。
討論問題:
在你的應(yīng)用場景中,你會如何權(quán)衡 NFC 的安全性 / 便捷性與 WLAN P2P 的高帶寬?
除了文件傳輸,你還能想到哪些創(chuàng)新的場景可以利用 HarmonyOS 的 NFC P2P 能力?

