16、WiFi Aware(邻居感知网络):WifiAwareManager介绍、发布与订阅服务、建立对等连接(Android 8+)
WiFi Aware,也叫邻居感知网络(NAN)。说实话,我第一次听到这个名字时,觉得它像某种科幻电影里的技术。但实际用起来,你会发现它解决了一个很实在的问题:设备之间怎么快速发现彼此,并且直接通信,不需要经过路由器或热点。
Android 8(API 26)开始,Google 正式把这个能力开放给了开发者。我当年在做一个线下社交 App 时,就踩过不少坑。今天我把这些经验整理出来,希望能帮你少走弯路。
什么是 WiFi Aware?
简单来说,WiFi Aware 让设备在不开热点、不连路由器的情况下,就能发现周围的其他设备,并建立连接。它工作在 2.4GHz 和 5GHz 频段,功耗比传统 WiFi 扫描低得多。
你可能会问:「这不就是蓝牙吗?」嗯,有点像,但 WiFi Aware 的传输速率更高,延迟更低,而且不需要配对过程。说白了,它更适合传输大文件或实时数据。
核心特点:
- 无需接入点(AP),设备间直接通信
- 低功耗发现,适合后台运行
- 支持 1 对 1 和 1 对多通信
- 基于 WiFi 的较高带宽
WifiAwareManager 介绍
WifiAwareManager 是 Android 提供的核心 API。要使用它,你得先获取系统服务:
WifiAwareManager awareManager =
(WifiAwareManager) context.getSystemService(Context.WIFI_AWARE_SERVICE);
if (awareManager == null || !awareManager.isAvailable()) {
// 设备不支持 WiFi Aware
return;
}
我个人习惯在 Application 初始化时就检查一次 availability。因为有些设备虽然跑着 Android 8+,但硬件不支持 WiFi Aware。提前检查可以避免运行时崩溃。
注意:WiFi Aware 需要硬件支持。不是所有 Android 8+ 设备都能用。建议在功能入口处做 availability 检查,并给出友好提示。
发布与订阅服务
WiFi Aware 的核心机制就是「发布-订阅」模式。一个设备发布服务,另一个设备订阅服务。当两者匹配时,系统会通知双方。
发布服务(Publish)
发布方需要创建一个 PublishConfig,指定服务名称和匹配规则:
PublishConfig config = new PublishConfig.Builder()
.setServiceName("com.example.my_aware_service")
.setMatchFilter(Arrays.asList(
new byte[]{0x01, 0x02},
new byte[]{0x03, 0x04}
))
.build();
awareManager.publish(config, new PublishDiscoverySessionCallback() {
@Override
public void onPublishStarted(PublishDiscoverySession session) {
// 发布成功,可以开始等待订阅者
}
@Override
public void onServiceDiscovered(PeerHandle peerHandle,
byte[] serviceSpecificInfo,
List<byte[]> matchFilter) {
// 有订阅者匹配到了我们的服务
}
}, null);
这里有个坑:setMatchFilter 的参数是 byte 数组列表。我曾经传了空列表,结果发现所有订阅者都能匹配到我的服务,导致连接混乱。后来才明白,空列表表示「匹配所有」,而不是「不匹配」。
订阅服务(Subscribe)
订阅方类似,但用的是 SubscribeConfig:
SubscribeConfig config = new SubscribeConfig.Builder()
.setServiceName("com.example.my_aware_service")
.setMatchFilter(Arrays.asList(
new byte[]{0x01, 0x02},
new byte[]{0x03, 0x04}
))
.build();
awareManager.subscribe(config, new SubscribeDiscoverySessionCallback() {
@Override
public void onSubscribeStarted(SubscribeDiscoverySession session) {
// 订阅成功
}
@Override
public void onServiceDiscovered(PeerHandle peerHandle,
byte[] serviceSpecificInfo,
List<byte[]> matchFilter) {
// 发现了匹配的发布者
}
}, null);
小技巧:服务名称建议用反向域名格式,避免与其他应用冲突。匹配过滤器可以传递自定义数据,比如版本号或设备类型,这样能实现更精细的匹配逻辑。
建立对等连接
发现对方后,下一步就是建立连接。WiFi Aware 使用 WifiAwareSession 来管理连接。发布方和订阅方都可以发起连接请求。
我一般让发布方主动发起连接,这样逻辑更清晰:
// 在 onServiceDiscovered 回调中
session.createNetworkSpecifier(peerHandle, new int[]{0}, new WifiAwareNetworkSpecifier.Builder()
.setPskPassphrase("my_shared_secret")
.build());
// 然后通过 WifiNetworkSpecifier 建立 Socket 连接
WifiNetworkSpecifier networkSpecifier = new WifiNetworkSpecifier.Builder()
.setWifiAwareNetworkSpecifier(awareNetworkSpecifier)
.build();
NetworkRequest networkRequest = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
.setNetworkSpecifier(networkSpecifier)
.build();
connectivityManager.requestNetwork(networkRequest, new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(Network network) {
// 网络可用,可以创建 Socket 了
}
});
嗯,这里要注意:createNetworkSpecifier 的第二个参数是 int[],表示支持的频段。0 表示 2.4GHz,1 表示 5GHz。我建议传 new int[]{0, 1},让系统自动选择最优频段。
避坑指南:我曾经在 onAvailable 回调里直接创建 Socket,结果发现连接超时。后来排查发现,网络虽然可用,但还需要等待几毫秒让路由表更新。建议加一个短暂延迟(比如 100ms)再创建 Socket。
知识体系总览
下面这张图总结了 WiFi Aware 的核心流程:
生命周期管理
WiFi Aware 的 session 和 callback 都需要妥善管理。我建议在 Activity 或 Fragment 的 onStop 中关闭 session:
@Override
protected void onStop() {
super.onStop();
if (awareSession != null) {
awareSession.close();
awareSession = null;
}
if (networkCallback != null) {
connectivityManager.unregisterNetworkCallback(networkCallback);
}
}
为什么?因为 WiFi Aware session 会占用系统资源。如果不及时释放,可能导致其他应用无法使用 WiFi Aware。我曾经在测试时忘了关闭 session,结果手机重启后才恢复正常。
建议:使用 LifecycleObserver 自动管理 session 生命周期,避免手动管理带来的遗漏。
实际项目中的注意事项
| 场景 | 建议 |
|---|---|
| 设备发现 | 设置合理的超时时间(默认 120 秒),避免无限等待 |
| 数据传输 | 使用 TCP 而非 UDP,WiFi Aware 的丢包率在复杂环境下较高 |
| 多设备 | 每个 peer 有唯一的 PeerHandle,注意不要混淆 |
| 安全 | 使用 PSK 加密,不要传输明文数据 |
最后说一句:WiFi Aware 是个好东西,但生态还不够成熟。如果你在项目中遇到奇怪的问题,不妨先检查一下设备兼容性。有时候不是代码的问题,是硬件不支持。
公众号:蓝海资料掘金营,微信deep3321