13、WifiAware(邻居感知):Aware 网络架构、服务发布与发现、数据传输
WifiAware,也叫邻居感知网络(NAN)。
说实话,我第一次听到这个名字时,第一反应是——这不就是 Wi-Fi 版的蓝牙吗?
后来深入研究了才发现,它比蓝牙强太多了。它不需要经过 AP,不需要连路由器,两个设备之间就能直接发现彼此、建立连接、传输数据。而且,它还能在后台低功耗地扫描周围的服务。
我曾在某个 IoT 项目中用过它。当时客户要求手机靠近智能音箱时,自动弹出控制界面。用蓝牙扫描?太慢。用 Wi-Fi Direct?又太重。最后选了 WifiAware,效果出奇的好。
13.1 Aware 网络架构
WifiAware 的核心思想,是让设备组成一个“集群”。
每个设备都可以成为集群中的一员。集群里有一个角色叫 Master,负责同步时钟、管理信标。其他设备是 Non-Master,它们可以休眠、可以醒来,只要在同步窗口内保持同步就行。
关键点:WifiAware 不依赖任何中心节点。每个设备都是对等的。即使 Master 离开了,集群会自动选举新的 Master。
我画了一张架构图,帮你理解这个集群是怎么运作的:
每个设备在加入集群时,会先发送一个 Discovery Beacon。Master 设备会定期发送 Synchronization Beacon,用来维持集群的时间同步。
你想想看,如果没有这个同步机制,设备各自睡各自的,那还怎么发现彼此?
13.2 服务发布与发现
WifiAware 最吸引我的地方,就是它的服务发布与发现机制。
传统 Wi-Fi 扫描,你得先连上同一个网络,然后才能互相通信。WifiAware 完全不需要。它直接在 2.4G 或 5G 频段上广播服务信息。
13.2.1 发布服务
发布服务,说白了就是告诉周围的设备:“嘿,我有这个功能,你可以来找我。”
// 创建 Aware 会话
WifiAwareSession session = wifiAwareManager.attach(new AttachCallback() {
@Override
public void onAttached(WifiAwareSession session) {
// 发布服务
PublishConfig config = new PublishConfig.Builder()
.setServiceName("com.example.my_service")
.setServiceSpecificInfo("hello".getBytes())
.build();
session.publish(config, new PublishCallback() {
@Override
public void onServiceDiscovered(PeerHandle peerHandle,
byte[] serviceSpecificInfo, List<byte[]> matchFilter) {
// 有设备发现了我的服务
}
}, null);
}
});
这里有个细节:setServiceSpecificInfo 可以携带一些自定义数据。我习惯用它来传递设备类型或版本号,这样对方可以提前做过滤。
我的经验:服务名称一定要有命名空间前缀,比如 com.example.xxx。否则容易跟其他应用冲突。我曾经遇到过两个不同厂商的 App 用了同一个服务名,结果互相干扰,排查了好久。
13.2.2 发现服务
发现服务,就是扫描周围有没有你感兴趣的服务。
// 发现服务
SubscribeConfig config = new SubscribeConfig.Builder()
.setServiceName("com.example.my_service")
.build();
session.subscribe(config, new SubscribeCallback() {
@Override
public void onServiceDiscovered(PeerHandle peerHandle,
byte[] serviceSpecificInfo, List<byte[]> matchFilter) {
// 找到了!可以发起连接了
}
}, null);
嗯,这里要注意:onServiceDiscovered 回调里拿到的 PeerHandle,只是一个临时句柄。你不能把它存起来下次用。每次发现都要重新获取。
13.3 数据传输
服务发现之后,就该传数据了。
WifiAware 的数据传输,走的是 Wi-Fi 直连通道。它会在两个设备之间建立一条安全的、点对点的链路。
13.3.1 建立连接
// 发起连接
session.createNetworkSpecifier(peerHandle, new NetworkSpecifierCallback() {
@Override
public void onNetworkSpecifierAvailable(NetworkSpecifier networkSpecifier) {
// 用这个 networkSpecifier 打开 Socket
WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
wifiManager.connect(networkSpecifier, new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(Network network) {
// 网络可用,可以传数据了
}
});
}
});
说白了,这一步就是把 Aware 的发现能力,跟标准的网络连接结合起来。你拿到 Network 之后,就可以用普通的 Socket 或 HTTP 来通信了。
13.3.2 传输方式
| 方式 | 说明 | 适用场景 |
|---|---|---|
| UDP | 无连接,速度快 | 实时数据、状态同步 |
| TCP | 可靠传输,有重传机制 | 文件传输、消息推送 |
| HTTP | 基于 TCP,上层协议 | REST API 调用 |
我个人习惯用 UDP 做心跳和状态同步,用 TCP 做文件传输。这样既保证了实时性,又保证了可靠性。
避坑指南:我曾经在传输大文件时,发现速度忽快忽慢。后来定位到是 MTU 问题。WifiAware 的链路层 MTU 默认是 1500,但实际可用载荷只有 1400 左右。如果你用 UDP 传超过 1400 字节的数据包,会被分片,导致丢包率飙升。建议应用层自己做分包,每个包控制在 1200 字节以内。
13.4 生命周期管理
WifiAware 的会话是有生命周期的。你发布或订阅服务后,如果应用退到后台,系统可能会暂停 Aware 会话。回到前台后,需要重新 attach。
我建议你在 onResume 和 onPause 里做 attach 和 detach 操作。这样既省电,又不会错过服务发现。
@Override
protected void onResume() {
super.onResume();
wifiAwareManager.attach(attachCallback, handler);
}
@Override
protected void onPause() {
super.onPause();
if (session != null) {
session.close();
session = null;
}
}
你想想看,如果不做这个管理,用户切到别的 App 再切回来,发现服务没了,体验多差。
13.5 总结
WifiAware 是一个被低估的技术。它比蓝牙快,比 Wi-Fi Direct 灵活,而且功耗控制得也不错。
如果你在做设备间近场通信、IoT 控制、或者社交类 App,我强烈建议你试试它。
当然,它也有缺点——目前 Android 设备的支持率还不够高。但趋势是向上的,Google 也在不断推进。
一句话总结:WifiAware 让设备像邻居一样互相感知,不需要中间人,不需要网络基础设施。发布、发现、传输,三步搞定。
公众号:蓝海资料掘金营,微信deep3321