13、WifiAware(邻居感知):Aware 网络架构、服务发布与发现、数据传输

WifiAware,也叫邻居感知网络(NAN)。

说实话,我第一次听到这个名字时,第一反应是——这不就是 Wi-Fi 版的蓝牙吗?

后来深入研究了才发现,它比蓝牙强太多了。它不需要经过 AP,不需要连路由器,两个设备之间就能直接发现彼此、建立连接、传输数据。而且,它还能在后台低功耗地扫描周围的服务。

我曾在某个 IoT 项目中用过它。当时客户要求手机靠近智能音箱时,自动弹出控制界面。用蓝牙扫描?太慢。用 Wi-Fi Direct?又太重。最后选了 WifiAware,效果出奇的好。

13.1 Aware 网络架构

WifiAware 的核心思想,是让设备组成一个“集群”。

每个设备都可以成为集群中的一员。集群里有一个角色叫 Master,负责同步时钟、管理信标。其他设备是 Non-Master,它们可以休眠、可以醒来,只要在同步窗口内保持同步就行。

关键点:WifiAware 不依赖任何中心节点。每个设备都是对等的。即使 Master 离开了,集群会自动选举新的 Master。

我画了一张架构图,帮你理解这个集群是怎么运作的:

WifiAware 集群架构 Master 设备 A Non-Master 设备 B Non-Master 设备 C Non-Master 设备 D Non-Master 设备 E 所有设备在同一个 Discovery Window 内同步

每个设备在加入集群时,会先发送一个 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。

我建议你在 onResumeonPause 里做 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