1. 内容整体设计与思路拆解
1.1 这一课到底在解决什么问题
第9课的核心是“碰一碰配网”。我先说说为什么我对这节课这么看重。做智能硬件场景的开发者应该都遇到过这个尴尬:一个不带屏幕、没有键盘的IoT设备(智能灯、摄像头、插座、白电),要让它连上家里的Wi-Fi,最常见的方案是让手机开热点、设备搜热点、手机再切回路由器……这套流程在客户那边演示一次,基本能把耐心耗光。二维码配网虽然比热点好一些,但摄像头对准暗光环境下的屏幕经常对焦失败,而且二维码图片一旦被转发,配网信息就泄露了。
NFC解决的是“操作路径”问题。手机NFC功能区贴近设备标贴,读到一个几十字节的NDEF消息,应用解析出Wi-Fi账号密码或一次性token,自动发起连接。整个过程不需要输入、不需要扫码、不需要用户理解任何技术细节。在HarmonyOS Next里实现这件事,核心就两块:一是NFC标签的读写能力,二是Wi-Fi连接能力,把两者串起来,就是一套标准的智能配网链路。
我拿这套方案给智能家居项目做过原型,演示效果比蓝牙配网稳定得多,因为蓝牙配网要做广播扫描、GATT连接、服务发现,链路长,出问题的环节多;NFC是一次性接触,读取成功率极高,只要标签和天线没坏,基本不会出现“搜不到设备”这种问题。
1.2 方案选型:为什么是NFC而不是蓝牙或二维码
这可能是很多人纠结的地方。我的看法是:配网方案不能只看技术,要看用户的使用场景和设备的硬件成本。
蓝牙配网的缺点是,手机需要靠近设备做扫描,用户在App里点“添加设备”后,界面会一直停在“搜索中”,如果设备没有正确进入配网模式,搜索超时要等十几秒。NFC配网是“确定性”的:你贴上去,读到了就是读到了,没读到立刻报错,没有玄学。
二维码配网的问题是,依赖手机摄像头,光线不好、屏幕贴膜反光时容易失败;另外二维码是个“可复制资产”,别人拍一张照就能拿到Wi-Fi信息。NFC标签虽然也可以被读,但至少需要物理贴近,攻击面小很多;而且NFC标签支持改写,配网完成后应用可以把标签内容覆盖为空,相当于“使用一次后作废”。
从HarmonyOS Next的开发角度来说,NFC标签能力的调用链很短:注册标签发现回调、解析NDEF消息、拿到载荷数据。Wi-Fi连接能力在API里也有现成接口。两件事都不需要引入第三方SDK,这对我这种不喜欢给工程增加依赖的人非常友好。
1.3 这节课的学习路径
我把整个实现拆成四步:
- 理解NFC的底层原理,知道NFC设备“碰一下”之后系统层发生了什么。
- 准备开发环境,处理真机调试和权限问题。
- 实现NFC标签的读取和解析,把标签里的NDEF消息转成业务数据。
- 联动Wi-Fi管理能力,完成从“碰一下”到“连上网”的完整闭环,最后再给一套问题排查清单。
每一个环节我都会把“为什么”讲透,而不只是贴一段代码。因为NFC系统回调触发时机、标签格式兼容性、Wi-Fi连接状态的异步处理,这些才是真正会在项目里卡住你的地方。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. NFC技术原理与HarmonyOS Next的适配机制
2.1 NFC基本原理:13.56MHz的近距离通信
NFC本质上就是RFID技术的一种,工作在13.56MHz频率,通信距离通常在10厘米以内。这个“近”是它的核心优势:天然适合“贴一贴”这种语义。NFC有三种工作模式,配网方案里主要用到的是读写模式——手机作为读卡器(Reader/Writer),主动去读NFC标签里的数据。另外两种模式是卡模拟(手机当作一张卡去刷闸机)和点对点(两台设备直接传数据),后面用到再单独讲。
一个NFC标签内部其实就是一个存储芯片加一个天线线圈。芯片里的存储区按照不同协议有不同的组织结构。大家常听到的Mifare Classic 1K,里面有16个扇区,每个扇区4个块,每块16字节,总容量1024字节。但对于应用层开发来说,直接跟扇区和块打交道的时候很少,因为系统通常会把标签按NDEF格式解析。
NDEF是NFC Forum定义的标准数据格式。你可以把NDEF消息看作一个“信封”,信封里面装着一条或多条记录(Record),记录里有类型、ID、载荷。比如一条文本记录的载荷是纯字符串,一条URI记录的载荷是网址。this is what makes the tag readable across devices。
在HarmonyOS Next的开发中,系统把NDEF标签的解析封装得比较干净。你注册监听后,底层已经把标签识别成NDEF消息,不需要自己去访问扇区。只有遇到极其特殊的非NDEF标签才需要动用底层接口。说实话,我开发到现在,90%的智能配网场景都只需要NDEF,所以把NDEF吃透就够了。
2.2 HarmonyOS Next的NFC能力封装
HarmonyOS Next把NFC能力放在@kit.ConnectivityKit里。具体的使用套路是:import { nfcController, tagSession } from '@kit.ConnectivityKit';。
nfcController负责全局的NFC开关状态、标签发现事件的注册;tagSession里是标签对象相关的类型和方法,比如TagInfo、NdefTag这些。真机贴标签的时候,系统会识别出标签类型,然后回调给你一个TagInfo对象,从这个对象里可以拿到NDEF标签实例,再进一步读取消息、解析记录、甚至写入新消息。
这个设计思路是“统一发现、分类处理”:不管标签是NDEF还是Mifare,系统先给你一个统一的TagInfo,你再根据自己的需求去做类型判断。如果你的设备同时支持NFC、蓝牙、UWB,你甚至可以在同一个回调里根据技术类型分发到不同的处理逻辑,这个我在多模设备联调时用过,非常顺手。
需要注意的是,HarmonyOS是“事件驱动”的:NFC标签不是你主动“拉取”到的,而是当手机贴近标签时,系统通过回调“推送”给你的。所以代码的正确姿势是:先注册回调,再把手机贴近标签。顺序反了,回调就收不到。
2.3 硬件适配与限制
HarmonyOS Next的NFC开发跟Android早年遇到的问题差不多:碎片化。不是所有华为手机都带NFC,部分平板的NFC支持也有限。更关键的是,模拟器完全模拟不了NFC,所以开发调试必须用真机。我自己的建议是,准备一台支持NFC的华为手机作为主力调试机,开发阶段最好固定机型,避免因为天线位置不同导致测试结果不稳定。
NFC感应区域通常在手机背面摄像头凸起附近,不是屏幕。我第一次调试的时候习惯性把标签贴在屏幕中央,结果半天没反应,后来才发现要贴背面,这个点值得重点提醒。
另外,NFC标签也有类型差异:有些是ISO 14443 Type A,有些是Type B,有些是FeliCa,但大多数市面上的空白NFC贴纸都是兼容Type A的。开发时尽量买质量好一点的空白标签,我之前图便宜买过一批劣质贴纸,写入成功后过两天再读就变成空白了,数据保存时间完全不可靠,做项目千万别省钱的地方。
3. 开发环境准备与权限处理
3.1 工程创建与依赖导入
用DevEco Studio新建一个空工程,选择“Empty Ability”模板,然后确认SDK版本。HarmonyOS Next 5.0(API 12)之后,NFC相关能力就集中在@ohos.nfc.controller和@ohos.nfc.tag模块下,5.0后面的版本又迁移到了@kit.ConnectivityKit。所以代码里能不能用nfcController直接导入,取决于你的SDK版本。
我写课例时用的是API 12及以上的版本,导入方式如下:
typescript复制import { nfcController, tagSession } from '@kit.ConnectivityKit';
如果编译报找不到模块,先检查build-profile.json5里的compileSdkVersion是否大于等于12。如果项目还在用老的@ohos.nfc.controller包名也能跑,但建议尽快迁移到Kit方式,因为新的SDK扩展特性都集中在Kit上。
工程里不需要额外安装三方依赖,这也是鸿蒙做系统能力集成的优势,NFC和Wi-Fi都是系统级Kit,直接在import就能用。
3.2 权限声明与运行时注意事项
这里有个很多人容易搞错的点:NFC读取本身在HarmonyOS Next上通常不需要向用户申请动态权限,属于系统开放能力。但是Wi-Fi连接相关的权限是另一回事。如果你要用wifiManager.connectToCandidateConfig这种方式连接指定Wi-Fi,大概率会涉及敏感权限,需要在module.json5里声明以下权限:
json复制{
"module": {
"requestPermissions": [
{ "name": "ohos.permission.GET_WIFI_INFO" },
{ "name": "ohos.permission.SET_WIFI_INFO" },
{ "name": "ohos.permission.MANAGE_WIFI_CONNECTION" }
]
}
}
不过要注意:MANAGE_WIFI_CONNECTION级别较高,普通应用可能拿不到。拿不到的时候怎么办?有两个办法:一是调用系统接口让用户手动选择Wi-Fi,应用只负责把NFC读到的信息展示出来;二是采用“系统级引导”的思路,跳转到系统WLAN设置页。我这个课例里用的是“尝试连接,失败则跳转系统设置”的兜底方案,因为自动连接涉及系统权限的变化,每个版本策略可能不一样,你的SDK手册为准。
另一个细节:贴标签时如果App不在前台,系统默认行为可能是不唤醒App。所以要保证使用场景是App打开后碰标签,或者在配置里声明后台读取能力,具体看官方文档说明。
3.3 真机调试的准备工作
真机调试前,建议先在开发者选项里打开“USB调试”,连接DevEco Studio,确保工程能部署到手机上。然后到手机设置里打开NFC开关。最后准备一张干净的NDEF标签。
一个建议:不要用一张很久之前写过乱七八糟内容的标签做测试。我在踩过坑后养成了习惯,每批标签拆开先用NFC工具格式化一次,确保存储区干净。在开发过程中,应用贴上去读到的是旧数据,你会误以为是自己的解析逻辑出了问题,排查到后来发现纯粹是标签残留数据,白白浪费一个小时。
如果你手上没有实物NFC标签,可以买几片NTAG213/215/216贴纸,几块钱成本,入门阶段完全够用。容量选NTAG213(144字节)就够了,智能配网场景根本用不了多少空间。
4. 核心实现:NFC标签读取与解析
4.1 注册标签发现回调
整个NFC读取的核心是nfcController.on('ndefTagDiscovered', callback)。ndefTagDiscovered是NDEF标签被扫描到后触发的事件。开发时建议在Page的onPageShow里注册,在onPageHide里取消注册,避免页面不可见时还在监听。
代码大致长这样:
typescript复制import { nfcController, tagSession } from '@kit.ConnectivityKit';
let ndefCallback = (tagInfo: tagSession.TagInfo) => {
// Android风格:先判断tagInfo里的技术列表,拿到NDEF标签对象
const ndefTag = tagInfo.getNdefTag();
if (!ndefTag) {
console.error('Tag is not NDEF compatible');
return;
}
// 读取NDEF消息
const ndefMessage = ndefTag.getNdefMessage();
if (!ndefMessage) {
console.error('No NDEF message found');
return;
}
parseNdefMessage(ndefMessage);
};
nfcController.on('ndefTagDiscovered', ndefCallback);
注意这个回调的名字和参数有可能随SDK版本调整,比如TagInfo的获取方式可能从tagSession.TagInfo变成别的形式。遇到编译报错不要慌,直接去看SDK里tagSession.d.ts的类型定义,搜getNdefTag就能找到答案。这也是我处理鸿蒙API变更的统一方法,外部资料老,但SDK类型是新的。
4.2 解析NDEF消息的结构
NDEF消息是一个记录数组。每一条记录最重要的字段是tnf(Type Name Format)、type(类型)和payload(载荷)。对配网场景来说,最方便的做法是把载荷直接当成字节数组转成字符串,再用JSON解析。
我这里写了一个简单的解析函数:
typescript复制function parseNdefMessage(message: tagSession.NdefMessage) {
const records = message.getRecords();
for (let i = 0; i < records.length; i++) {
const record = records[i];
const tnf = record.getTnf();
const type = record.getType();
const payload = record.getPayload();
// 把payload转成字符串
const text = String.fromCharCode.apply(null, new Uint8Array(payload));
console.info(`Record ${i}: tnf=${tnf}, type=${type}, payload=${text}`);
// 尝试按JSON解析
try {
const config = JSON.parse(text);
if (config.ssid && config.password) {
handleWifiConfig(config);
}
} catch (err) {
console.error('Payload is not JSON, ignore');
}
}
}
这里有个细节:NDEF文本记录(TNF为1)的payload首字节是语言码长度,后面才是语言码和文本内容;URI记录(TNF为2)的payload首字节是URI前缀标识,后面才是去掉前缀的网址。所以如果标签是拿手机自带“写入Wi-Fi信息”功能写的,你读到的payload可能需要跳过首字节才能真正拿到文本。我课例里因为是自己构造的NDEF记录,所以直接把payload转成字符串,但如果你读的是第三方工具写的标签,注意做这个偏移处理。
4.3 反向操作:往标签里写配网信息
有时候配网流程不是“设备已经贴好标签”,而是“用户拿到设备后第一次配网”。这时我们可以让应用反向往标签里写一条NDEF消息。写入的逻辑和读类似,先拿到NdefTag对象,再调用writeNdefMessage方法。
构造NDEF消息的代码:
typescript复制import { tagSession, util } from '@kit.ConnectivityKit';
let encoder = new util.TextEncoder();
let payload = encoder.encodeInto(JSON.stringify({
ssid: 'MyHomeWiFi',
password: 'p@ssw0rd',
timestamp: Date.now()
}));
let record = tagSession.NdefRecord.createTextRecord(payload);
let message = new tagSession.NdefMessage([record]);
ndefTag.writeNdefMessage(message).then(() => {
console.info('Write NDEF message success');
}).catch((err: Error) => {
console.error(`Write failed: ${err.message}`);
});
这段代码里的createTextRecord会把文本封装成标准文本记录,适合大多数NFC阅读器读取。写入时需要确保标签未写保护、存储空间够用。另外在写入过程中手机要保持贴近标签,不要手抖移开,否则容易写一半失败。
我建议配网类标签使用后直接覆盖为空消息,这个后面安全部分再详细说。
5. 智能配网:从读到连的完整链路
5.1 配网数据格式设计
读到了NFC数据之后,下一个问题是用什么结构承载配网信息。我在项目里的约定是:标签里存一个JSON对象,包含字段如下。
| 字段 | 类型 | 说明 |
|---|---|---|
| v | number | 协议版本号,便于后续兼容扩展 |
| ssid | string | Wi-Fi网络名称 |
| password | string | Wi-Fi密码 |
| token | string | 设备绑定用的一次性令牌 |
| expire | number | 令牌过期时间戳(毫秒) |
| band | string | 可选,指定2.4G或5G频段,默认auto |
为什么字段要这么设计?首先是v版本号,设备端或App后续可以据此判断是否支持该标签格式;token和expire是为了安全,避免密码明文长期暴露;band是为了应对双频路由器的兼容问题,很多IoT设备不支持5G频段,明确标注可以减少无效连接。
但要注意NFC标签存储极小,NTAG213也就一百多字节,所以JSON别写得太大,字段名尽量短,也不要把设备绑定大字段塞进去。核心原则是“NFC只做入口,重数据走云端”。
5.2 Wi-Fi连接调用全景代码
拿到配网配置后,开始连接Wi-Fi。这里的复杂点在于鸿蒙的Wi-Fi接口有多个层级。如果条件允许,优先使用wifiManager.isConnected检查当前网络,如果已经连接,就直接跳过;如果没有,尝试调用wifiManager.connectToCandidateConfig或系统提供的连接接口。
我课例写了一个简化版本:
typescript复制import { wifiManager } from '@kit.ConnectivityKit';
async function handleWifiConfig(config: WifiConfig): Promise<void> {
// 1. 检查当前Wi-Fi是否已经连接
const activeNetwork = wifiManager.getLinkedInfo();
if (activeNetwork && activeNetwork.ssid === config.ssid) {
console.info('Already connected to target wifi');
return;
}
// 2. 构建设备配置
const candidateConfig: wifiManager.WifiDeviceConfig = {
ssid: config.ssid,
preSharedKey: config.password,
securityType: wifiManager.SecurityType.WPA2
};
// 3. 尝试连接
try {
const result = await wifiManager.addCandidateConfig(candidateConfig);
console.info(`Add candidate config result: ${result}`);
// 等几秒检查是否连接成功
setTimeout(async () => {
const info = await wifiManager.getLinkedInfo();
if (info && info.ssid === config.ssid) {
console.info('Wifi connected success');
// TODO: 上报配网结果或继续设备绑定流程
} else {
console.error('Wifi connected failed');
}
}, 5000);
} catch (err) {
console.error(`Connect wifi error: ${JSON.stringify(err)}`);
}
}
这段代码里有几个注意点。
第一,WifiDeviceConfig里的securityType要和你路由器加密方式匹配,现在大部分家用路由器是WPA2或WPA3,如果标签里没有加密方式字段,可以默认按WPA2处理。
第二,addCandidateConfig的结果是“添加配置成功”,不代表“连接成功”。网络是异步连接的,所以需要轮询getLinkedInfo的结果来判断最终状态。
第三,如果应用没有调用系统连接接口的权限,addCandidateConfig可能被拒绝。这种情况下,我建议直接跳转系统WLAN设置页:
typescript复制import { settings } from '@kit.ArkTS';
settings.startAbility('settings://wifi');
把选择权交还给用户。虽然体验上多了一步,但至少保证链路是通的。
5.3 从NFC触发到配网成功,完整流程串讲
下面是我实际调试时常用的完整流程:
- 应用启动,页面注册NFC标签监听。
- 用户拿手机贴近设备上的NFC标签。
- 系统回调
ndefTagDiscovered,拿到NDEF标签。 - 解析NDEF消息为JSON配置。
- 检查配置里的
token和expire,如果过期就提示用户重新生成标签。 - 检查当前Wi-Fi连接状态,如果已经连接目标热点则直接跳转下一步。
- 调用Wi-Fi连接相关接口,尝试连接目标路由器。
- 轮询连接状态,连接成功后把结果上报云端,并引导用户完成设备绑定。
- 配网完成后,应用主动调用标签的
clearNdefMessage或writeNdefMessage写入空消息,清空敏感信息。 - 如果中间任何一步失败,给出明确错误提示,比如“请靠近标签”“当前网络不可用”“连接超时”。
这一步设计看似繁琐,但每一条都是从真实测试中沉淀出来的。比如第5步,如果不做有效期校验,别人拿一张标签贴一下就能篡改你的Wi-Fi配置;第9步,配网完成后不清空标签,标签上的密码就一直留在物理实体的存储里。
5.4 无屏设备的实际场景演示
拿一盏智能台灯举例。台灯出厂时贴了一个NFC标签,App首次开机引导时进入“添加设备”,把手机靠近台灯标签,读出的JSON配置包含家里的Wi-Fi ssid和password。App自动连接Wi-Fi后,台灯通过局域网广播被发现,App完成设备绑定。整个过程大概5秒,用户不需要懂“热点”“局域网”是什么概念。
如果换成蓝牙配网,用户要把台灯调到热点模式,手机连接台灯的热点,此时手机会短暂断网,然后切回来,光这部分网络切换的等待就要10秒;NFC方案根本不需要切换网络,读取标签瞬间完成,剩下只是Wi-Fi连接本身的时间,体感完全不同。
6. 常见问题与安全使用建议
6.1 高频问题速查表
下面这些是我在开发和做技术支持时遇到的典型问题,整理成表格方便查阅。
| 现象 | 可能原因 | 处理方法 |
|---|---|---|
| 贴近标签无反应 | 手机NFC开关没打开 | 到设置里开启NFC |
| 贴近标签无反应 | 标签贴在手机屏幕而非背面 | 把标签贴到背面摄像头附近 |
| 回调收到了但解析为空 | 标签不是NDEF格式,或为空标签 | 用NFC工具格式化标签为NDEF |
| 读出来乱码 | NDEF文本记录带语言码前缀 | 解析payload时跳过首字节 |
| 写标签失败 | 标签写保护或耐久度耗尽 | 换新标签 |
| 写标签失败 | 写入过程中手机移开 | 贴住标签保持1-2秒再移开 |
| Wi-Fi连不上 | 设备只支持2.4G,路由器只开了5G | 开启双频或改用2.4G |
| Wi-Fi连不上 | 路由器开了AP隔离 | 关闭AP隔离 |
| Wi-Fi连不上 | 密码含特殊字符 | 考虑NFC写入时做URI/JSON转义 |
第2个问题我在前面的课里也提过,但这里还是要重复,因为它是“看起来好像代码没生效,其实只是天线没对准”的经典误判。
6.2 “扇区”和底层标签技术,什么时候才用得到
有同学会问,网上有些人说要看NFC扇区,怎么在鸿蒙里看?我说明一下,我们日常智能配网、公交卡、门禁卡这类应用,标准做法是走NDEF统一封装,不需要关心扇区。NFC扇区和块是Mifare Classic这类卡片的存储概念,属于相对底层的内容。
只有当你处理特定行业硬件(比如老的考勤系统、停车卡)时,才需要直接读取扇区数据。那些卡通常不是NDEF格式,而是厂商自定义格式,系统识别后返回的技术栈里会包含MifareClassic标签对象。你可以通过相应的接口读取原始块数据,但前提是你有合法的授权和业务需求。
我也经常看到网上有些“NFC解码工具”“NFC破解”的说法,这里提一句:开发调试就用官方提供的API和正规的NFC读写工具,那些来路不明的第三方“全功能”工具不仅容易把标签写到坏,还可能窃取标签里的数据,千万别碰。
6.3 安全使用建议:标签不怕物理贴近,但怕信息泄露
NFC本质上是“不加密的广播信道”,任何支持NFC的手机靠近都能读到标签内容。所以标签上的数据必须当成“公开信息”来看待,不能存长期有效的核心凭证。
我的安全设计原则有三条。
第一,时效性。标签里的token必须带过期时间,哪怕被复制了,几分钟后也失效。这能有效降低“标签被贴一下子,Wi-Fi信息被抄走”的风险,也可以缓解大家对中继复制的担忧。
第二,一次性。配网成功后就清空标签内容。设备本身已经记住了网络信息,标签的存在意义已经结束,再留着就是风险。这一步简单,但很多人会漏。
第三,最小化。标签里能不放Wi-Fi密码就不放,优先放设备标识和一次性授权码,设备通过云端获取真正的网络凭据。当然这需要设备支持联网,且云服务可用,如果设备还没有任何网络连接,这条不成立。现实项目中,多数带屏或带网口的设备会走云端方案,成本低的轻量设备就只能用“放密码但加密”的折中方案。
6.4 实操排错心得
最后分享几个我自己的调试心得。
第一,善用hilog。NFC标签回调的关键日志可以打上同一个标签,比如NfcSmartConfig,调试时用hilog | grep NfcSmartConfig过滤,效果非常好。鸿蒙的日志输出在DevEco Studio的Log面板里也能看,但命令行过滤更快。
第二,准备至少三张标签轮换测试。写坏一张换一张,不要在同一张标签上反复写,标签的存储单元耐久度是有限的,次数多了写入会静默失败。
第三,做配网功能时把Wi-Fi连接逻辑单独抽成一个工具类,不要写在UI控制器里。因为NFC回调、Wi-Fi回调、页面生命周期交织在一起,代码一乱排查起来会很痛苦。我课上写的示例代码是偏向演示的,正式项目建议拆成NfcService和WifiConnector两个类。
第四,如果遇到SDK接口变动,第一时间查SDK目录下的.d.ts文件,别看网上那些可能过时的博客。HarmonyOS Next的API还有调整,能编译通过加上运行时日志,才是唯一的准绳。
我做NFC配网功能最有感触的一点是:技术的价值不在于代码多炫,而在于用户“碰一下”就能完成配网,这种体验的顺畅度是硬件时代最好的名片。顺着这个思路,下一步可以学设备之间的点对点NFC传输,或者把配网流程接到鸿蒙的元服务上,玩法会更多。
