10、Wi-Fi 直连(P2P):WifiP2pService、P2P 状态机、设备发现与连接
Wi-Fi 直连,说白了就是让两个设备不经过路由器,直接“手拉手”通信。我刚开始接触这个模块时,觉得它不就是个简化版的 Wi-Fi 吗?后来踩了坑才明白,P2P 的复杂性全藏在状态机里。
今天我们就来拆解 Android 的 P2P 实现。核心有三个东西:WifiP2pService、P2P 状态机、以及设备发现与连接的流程。
10.1 WifiP2pService:P2P 的大脑
WifiP2pService 是系统服务,运行在 system_server 进程里。它负责管理所有 P2P 相关的操作。
我个人习惯把它看作一个“指挥官”。它不直接干活,而是把任务派发给底层的 wpa_supplicant。wpa_supplicant 才是真正跟 Wi-Fi 芯片打交道的家伙。
WifiP2pService 启动时,会做几件关键的事:
- 注册 P2P 相关的广播接收器
- 初始化 P2P 状态机
- 建立与 wpa_supplicant 的通信通道
嗯,这里要注意:WifiP2pService 和 WifiService 是独立的。它们各自管理不同的功能,但共享底层的 Wi-Fi 硬件。所以你不能同时用 Wi-Fi 和 P2P,除非硬件支持并发。
10.2 P2P 状态机:一切的核心
P2P 状态机是整个模块的灵魂。我见过不少开发者直接调用 API,结果发现设备发现成功了,连接却失败了。为什么?因为状态机没走对。
Android 的 P2P 状态机定义在 WifiP2pStateMachine 中。它包含以下几个主要状态:
| 状态 | 说明 | 常见触发 |
|---|---|---|
| P2pDisabledState | P2P 功能关闭 | Wi-Fi 关闭时 |
| P2pEnablingState | 正在开启 P2P | 调用 setWifiP2pEnabled(true) |
| P2pEnabledState | P2P 已就绪 | wpa_supplicant 初始化完成 |
| InactiveState | 空闲,可发起发现 | P2P 开启后默认状态 |
| ProvisionDiscoveryState | 正在协商参数 | 收到连接请求 |
| GroupNegotiationState | 正在协商群组 | 双方确认连接 |
| GroupCreatedState | 群组已建立 | 协商成功 |
状态机之间通过消息传递来切换。比如,当设备发现完成后,会发送 CONNECT 消息,状态机就从 InactiveState 跳到 ProvisionDiscoveryState。
我曾经在调试一个 P2P 连接失败的问题时,发现状态机卡在了 GroupNegotiationState。查了半天,原来是 wpa_supplicant 返回了一个错误的 GO Intent 值。你想想看,这种问题如果不理解状态机,根本无从下手。
10.3 设备发现:找到对方
设备发现是 P2P 的第一步。应用层调用 discoverPeers() 后,WifiP2pService 会向 wpa_supplicant 发送 P2P_FIND 命令。
wpa_supplicant 会在 2.4GHz 频段上发送 Probe Request 帧,并监听 Probe Response。这个过程有点像你在黑夜里喊一嗓子,然后等别人回应。
发现的结果通过广播返回:
// 注册广播接收器
IntentFilter filter = new IntentFilter();
filter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
registerReceiver(receiver, filter);
// 在接收器中处理
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
// 请求更新设备列表
manager.requestPeers(channel, peerListListener);
}
}
这里有个坑:requestPeers() 是异步的,结果通过回调返回。我刚开始写的时候,直接在广播里取设备列表,结果发现是空的。后来才明白,广播只是通知你有变化,真正的数据要通过回调拿。
stopPeerDiscovery()。我曾经见过一个 App 忘记停止发现,导致手机发热严重。
10.4 连接过程:从发现到握手
发现设备后,下一步就是连接。连接过程分为三个阶段:
- Provision Discovery:双方交换能力信息,比如支持的 WPS 类型、设备类型等。
- Group Negotiation:协商谁当 Group Owner(GO),谁当 Client。GO 的角色很重要,它负责管理整个 P2P 群组。
- WPS 握手:使用 Wi-Fi Protected Setup 完成认证和加密。
连接代码看起来很简单:
// 连接指定设备
manager.connect(channel, config, new WifiP2pManager.ActionListener() {
@Override
public void onSuccess() {
// 连接请求已发送
}
@Override
public void onFailure(int reason) {
// 连接失败,reason 包含错误码
}
});
但实际流程远比这复杂。比如,Group Owner 会启动一个 SoftAP,Client 则像连接普通 Wi-Fi 一样去连接这个 SoftAP。你想想看,这中间涉及 IP 地址分配、DHCP、DNS 等一系列网络协议。
我建议你在调试时,同时抓取 wpa_supplicant 的日志。命令是:
adb shell logcat -b main -s wpa_supplicant
这样能看到 P2P 协商的每一个步骤,比只看 Java 层的日志清晰得多。
10.5 核心流程总览
为了让你更直观地理解整个流程,我画了一张图。它展示了从开启 P2P 到建立连接的全过程。
从图中可以看到,整个流程是分层递进的。每一层只关心自己的职责,上层不关心下层的实现细节。这也是 Android 系统架构的一贯风格。
10.6 避坑指南
最后,分享几个我实际项目中遇到的坑:
- 并发问题:P2P 和 Wi-Fi 不能同时使用。如果你在连接 P2P 时打开了 Wi-Fi,P2P 会被强制断开。我曾经因为这个被测试妹子追着问了一下午。
- 权限问题:Android 6.0 之后,P2P 需要
ACCESS_FINE_LOCATION权限。因为扫描结果可以用于定位。嗯,这个坑很多新手会踩。 - 设备兼容性:不同厂商的 Wi-Fi 芯片对 P2P 的支持程度不同。有些老设备在 Group Negotiation 阶段会超时。我建议你在代码里加一个重试机制。
- WPS 限制:Android 9 之后,WPS 的 PIN 模式被废弃了。如果你还在用 PIN 方式连接,会发现某些设备连不上。
P2P 的开发,说白了就是跟状态机打交道。理解了状态机的流转,你就掌握了 P2P 的命脉。剩下的,不过是填参数、调回调而已。
公众号:蓝海资料掘金营,微信deep3321