
一、这个功能解决什么问题
针对设备互联的场景,HarmonyOS 提供了三个不同级别的 Wi-Fi 进阶能力:
| 能力 | 适用场景 | 是否需要路由器 | 典型延迟 |
|---|---|---|---|
| Wi-Fi 热点 | 设备共享网络、临时组网 | 否(设备自身模拟) | 高,类似普通 AP |
| Wi-Fi P2P | 高速文件传输、投屏 | 否(点对点直连) | 低,峰值可达数百 Mbps |
| Wi-Fi Aware | 邻近发现、轻量匹配 | 否(设备间直接感知) | 极低,主要用于发现 |
核心原则:能用 Aware 发现,就用 Aware;发现后需要传输,切换到 P2P;仅需要共享网络,开热点。
这个设计思路很清晰,但实际开发中有不少隐藏的约束,下面逐一展开。
二、环境说明
text
DevEco Studio 版本:DevEco Studio 6.1.0 及以上
HarmonyOS SDK 版本:HarmonyOS 6.1.0 (23) 及以上
目标设备:手机(需支持 Wi-Fi 6 或 Wi-Fi Aware 功能)
三、Wi-Fi 热点 ------ 让设备成为 AP
1. 核心 API
typescript
import { wifiManager } from '@kit.ConnectivityKit';
创建热点有两个关键 API:enableHotspot 和 disableHotspot。
很多人在升级到 API 11 后,发现传入的参数和旧版本不一样了。新版本要求传入 HotspotConfig 结构体,并且需要先获取 wifiManager 实例,不再像以前那样直接调用静态方法。
2. 完整示例代码
typescript
// HotspotDemo.ets
import { wifiManager } from '@kit.ConnectivityKit';
import { common } from '@kit.AbilityKit';
@Entry
@Component
struct HotspotDemo {
@State isHotspotOn: boolean = false;
@State statusText: string = '热点未开启';
build() {
Column({ space: 16 }) {
Text(this.statusText)
.fontSize(16)
.margin({ bottom: 16 })
Button(this.isHotspotOn ? '关闭热点' : '开启热点')
.onClick(async () => {
if (this.isHotspotOn) {
await this.disableHotspot();
} else {
await this.enableHotspot();
}
})
}
.padding(24)
.width('100%')
.height('100%')
}
// 开启热点
private async enableHotspot(): Promise<void> {
try {
// 检查是否已开启
const isActive: boolean = wifiManager.isHotspotActive();
if (isActive) {
this.statusText = '热点已经开启';
this.isHotspotOn = true;
return;
}
// 构造热点配置
const config: wifiManager.HotspotConfig = {
ssid: 'HarmonyOS_Hotspot', // 自定义 SSID
securityType: wifiManager.SecurityType.WPA2_PSK, // 安全类型
preSharedKey: '12345678', // 密码,至少8位
bandwidth: wifiManager.Bandwidth.BANDWIDTH_2_4_G,
channel: 1, // 可选,不传系统自动分配
};
const success: boolean = await wifiManager.enableHotspot(config);
if (success) {
this.isHotspotOn = true;
this.statusText = '热点已开启,SSID: ' + config.ssid;
} else {
this.statusText = '开启失败,请检查权限或系统限制';
}
} catch (err) {
this.statusText = '异常: ' + JSON.stringify(err);
}
}
// 关闭热点
private async disableHotspot(): Promise<void> {
try {
const success: boolean = await wifiManager.disableHotspot();
if (success) {
this.isHotspotOn = false;
this.statusText = '热点已关闭';
}
} catch (err) {
this.statusText = '关闭异常: ' + JSON.stringify(err);
}
}
}
3. 注意事项
- 密码必须不少于 8 位 ,否则
enableHotspot直接返回 false。 securityType不支持OPEN时,必须指定 WPA2 或 WPA3。- 开启热点后,设备自身的 Wi-Fi 连接会被断开,这是硬件限制。
- 模拟器上不支持热点功能,必须真机测试。
四、Wi-Fi P2P ------ 高速直连传输
P2P 的主要流程:发现对端 -> 创建组或连接 -> 传输数据。
1. 关键 API
typescript
import { p2pManager } from '@kit.ConnectivityKit';
需要关注的几个方法:
p2pManager.createGroup(iface)------ 创建 P2P 组(设备作为组所有者)p2pManager.connectToDevice(iface, deviceConfig)------ 连接特定设备p2pManager.stopPeerDiscovery(iface)------ 停止发现
2. 完整 P2P 连接示例
typescript
// P2PDemo.ets
import { p2pManager, wifiManager } from '@kit.ConnectivityKit';
interface WifiP2pDevice {
deviceName: string;
deviceAddress: string;
}
@Entry
@Component
struct P2PDemo {
@State discoveredDevices: WifiP2pDevice[] = [];
@State connectionStatus: string = '未连接';
// P2P 监听器
private listener: p2pManager.P2pListener | null = null;
aboutToAppear() {
this.registerP2pListener();
}
aboutToDisappear() {
this.unregisterP2pListener();
}
build() {
Column({ space: 12 }) {
Text('P2P 直连')
.fontSize(24)
.fontWeight(FontWeight.Bold)
Text(this.connectionStatus)
.fontSize(14)
.margin({ bottom: 16 })
Button('发现对端')
.onClick(() => this.discoverPeers())
List({ space: 8 }) {
ForEach(this.discoveredDevices, (device: WifiP2pDevice, index: number) => {
ListItem() {
Row() {
Text(device.deviceName)
.fontSize(16)
Button('连接')
.onClick(() => {
this.connectToDevice(device.deviceAddress);
})
}
.justifyContent(FlexAlign.SpaceBetween)
.padding(12)
.border({ width: 1, color: Color.Gray })
}
})
}
.width('100%')
.height(300)
Button('创建组(作为所有者)')
.onClick(() => this.createGroup())
}
.padding(24)
.width('100%')
}
// 注册 P2P 事件监听
private registerP2pListener(): void {
const iface = 'wlan0'; // 大部分设备接口名
this.listener = {
onP2pStateChanged: (state: p2mManager.P2pState) => {
console.info('P2P state changed:', state);
},
onP2pDevicesFound: (devices: p2pManager.WifiP2pDevice[]) => {
// 发现设备列表更新
this.discoveredDevices = devices.map(d => ({
deviceName: d.deviceName,
deviceAddress: d.deviceAddress,
}));
console.info('Found devices:', this.discoveredDevices.length);
},
onP2pConnectionChanged: (info: p2pManager.P2pInfo) => {
if (info.groupFormed && info.isGroupOwner) {
this.connectionStatus = '作为群主,已连接';
} else if (info.groupFormed) {
this.connectionStatus = '已连接至群主';
} else {
this.connectionStatus = '未连接';
}
},
};
p2pManager.on('p2pStateChange', this.listener);
p2pManager.on('p2pDeviceChange', this.listener);
p2pManager.on('p2pConnectionChange', this.listener);
}
private unregisterP2pListener(): void {
if (this.listener) {
p2pManager.off('p2pStateChange', this.listener);
p2pManager.off('p2pDeviceChange', this.listener);
p2pManager.off('p2pConnectionChange', this.listener);
this.listener = null;
}
}
private async discoverPeers(): Promise<void> {
try {
const iface = 'wlan0';
const found = await p2pManager.discoverPeers(iface, 10_000); // 超时10秒
if (found) {
console.info('Discovery started, waiting for devices...');
}
} catch (err) {
console.error('Discovery failed:', err);
}
}
private async connectToDevice(address: string): Promise<void> {
try {
const iface = 'wlan0';
const config: p2pManager.DeviceConfig = {
deviceAddress: address,
groupOwnerIntent: 3, // 0-15,数值越小越倾向于作为客户端,越大越作为群主
networkId: -1, // -1 表示任意网络
passphrase: '12345678', // 群主需要设置的密码
};
const success = await p2pManager.connectToDevice(iface, config);
if (success) {
this.connectionStatus = '连接发起成功,等待对方确认';
}
} catch (err) {
console.error('Connection failed:', err);
}
}
private async createGroup(): Promise<void> {
try {
const iface = 'wlan0';
const config: p2pManager.GroupConfig = {
networkName: 'P2P-Group',
passphrase: '87654321',
};
const success = await p2pManager.createGroup(iface, config);
if (success) {
this.connectionStatus = 'P2P 组创建成功,等待客户端连接';
}
} catch (err) {
console.error('Create group failed:', err);
}
}
}
3. 核心注意事项
- P2P 连接成功后,设备之间的通信可以通过 Socket 进行,建议使用 TCP 或 UDP。
groupOwnerIntent决定了优先级,如果两个设备都想当群主,会协商失败。- 监听器的注册要在
aboutToAppear中完成,并在aboutToDisappear中注销,避免页面销毁后回调导致的状态更新。
五、Wi-Fi Aware ------ 设备发现与轻量通信
Aware 的核心思路:设备在不连接 AP 的情况下,通过超低功耗发现邻近服务。
1. 关键 API
aware.subscribe 订阅服务,aware.publish 发布服务。
2. 完整示例
typescript
// AwareDemo.ets
import { aware } from '@kit.ConnectivityKit';
@Entry
@Component
struct AwareDemo {
@State serviceStatus: string = '未发布/订阅';
@State foundServices: string[] = [];
private serviceId: number | null = null;
build() {
Column({ space: 16 }) {
Text(this.serviceStatus)
.fontSize(14)
Button('发布服务')
.onClick(() => this.publishService())
Button('订阅周围服务')
.onClick(() => this.subscribeService())
List() {
ForEach(this.foundServices, (service: string, index: number) => {
ListItem() {
Text('发现: ' + service)
}
})
}
.width('100%')
.height(200)
}
.padding(24)
}
private async publishService(): Promise<void> {
try {
// 发布一个本地服务
const config: aware.PublishConfig = {
serviceName: 'HarmonyOS Demo Service',
serviceSpecificInfo: new Uint8Array([0x01, 0x02, 0x03]), // 自定义信息
matchFilter: new Uint8Array([0x01]), // 过滤条件
publishType: aware.PublishType.UNSOLICITED, // 主动发布
ttlSeconds: 300, // 存活时间
};
const publishId = await aware.publish(config);
if (publishId !== null) {
this.serviceId = publishId;
this.serviceStatus = '服务已发布, ID: ' + publishId;
}
} catch (err) {
this.serviceStatus = '发布失败: ' + err.message;
}
}
private async subscribeService(): Promise<void> {
try {
const config: aware.SubscribeConfig = {
serviceName: 'HarmonyOS Demo Service', // 只订阅同名服务,不传则所有
matchFilter: new Uint8Array([0x01]),
subscribeType: aware.SubscribeType.ACTIVE, // 主动轮询
};
// 订阅回调
const subscribeId = await aware.subscribe(config, (discoveredServices: aware.DiscoveredService[]) => {
// 每次发现更新都会触发
this.foundServices = discoveredServices.map(s => s.serviceName);
console.info('Aware services found:', this.foundServices);
});
if (subscribeId !== null) {
this.serviceStatus = '订阅成功, ID: ' + subscribeId;
}
} catch (err) {
this.serviceStatus = '订阅失败: ' + err.message;
}
}
}
3. 坑点说明
- Aware 需要设备硬件支持,目前仅部分中高端机型具备。
publish和subscribe的matchFilter必须匹配,否则两端无法发现对方。- 订阅的回调是异步的,且频率可能较高,不要在回调里做耗时操作或者频繁 setState。
六、综合场景:P2P 文件传输
结合 Aware 发现 + P2P 传输,实现一个文件共享场景。
流程:
- A 设备发布 Aware 服务 (表示"我可共享文件")
- B 设备订阅 Aware 服务,发现 A
- B 通过 A 的 Aware 信息获取 IP/端口,切换到 P2P 连接
- P2P 建立后,通过 Socket 传输文件
由于篇幅,这里给出简化版的 P2P 传输代码,Aware 发现部分参照上一节。
typescript
// FileTransferDemo.ets
import { socket } from '@kit.NetworkKit';
import { p2pManager } from '@kit.ConnectivityKit';
@Entry
@Component
struct FileTransferDemo {
@State transferStatus: string = '等待连接';
private serverSocket: socket.TCPSocket | null = null;
private clientSocket: socket.TCPSocket | null = null;
build() {
Column({ space: 16 }) {
Text(this.transferStatus)
.fontSize(16)
Button('启动服务端(接收文件)')
.onClick(() => this.startServer())
Button('连接并发送数据')
.onClick(() => this.connectAndSend())
}
.padding(24)
}
// 作为服务端监听连接
private async startServer(): Promise<void> {
try {
this.serverSocket = new socket.TCPSocket();
// 绑定本地端口,注意 P2P 接口通常是 192.168.49.x
await this.serverSocket.bind({ address: '0.0.0.0', port: 8888, family: 1 });
await this.serverSocket.listen(1); // 最多接受一个连接
this.serverSocket.on('message', (data: ArrayBuffer) => {
// 收到数据,简单转成字符串
const decoder = new util.TextDecoder('utf-8');
const text = decoder.decodeToString(new Uint8Array(data));
this.transferStatus = '接收到: ' + text;
});
this.transferStatus = '服务端已启动,等待连接...';
} catch (err) {
this.transferStatus = '启动服务端失败: ' + err.message;
}
}
// 作为客户端连接并发送
private async connectAndSend(): Promise<void> {
try {
// 实际项目中,这个 address 来自 Aware 发现或 P2P 连接后的 IP
const P2P_SERVER_IP = '192.168.49.1'; // 群主的典型 IP
const PORT = 8888;
this.clientSocket = new socket.TCPSocket();
await this.clientSocket.connect({ address: P2P_SERVER_IP, port: PORT, family: 1 });
// 发送一个字符串(实际文件可用流式发送)
const encoder = new util.TextEncoder();
const data = encoder.encodeInto('Hello from HarmonyOS P2P!');
await this.clientSocket.send(data);
this.transferStatus = '数据发送成功';
this.clientSocket.close();
} catch (err) {
this.transferStatus = '发送失败: ' + err.message;
}
}
}
常见问题
Q:为什么 P2P 发现不到设备?
可能是两台设备未在同一信道上。建议先开启 Wi-Fi 扫描,让两者都处于扫描状态。此外,部分定制 ROM 限制了 P2P 功能。
Q:Aware 在模拟器上能跑吗?
不能。Aware 依赖硬件支持,模拟器无法模拟射频信号。必须使用真机测试,并且该机型需支持 Wi-Fi Aware。
Q:开启热点后,为什么 P2P 无法使用?
同设备上热点和 P2P 无法共存。如果你正在使用热点分享网络,P2P 功能会被系统限制,需要先关闭热点。
Q:P2P 连接成功后,如何获取对方的 IP 地址?
可以通过 p2pManager.getP2pInfo 获取,返回的信息中包含 groupOwner 的 IP 地址,客户端地址需要从 Socket 连接中获取。