10、Wi-Fi 直连(P2P):WifiP2pService、P2P 状态机、设备发现与连接

Wi-Fi 直连,说白了就是让两个设备不经过路由器,直接“手拉手”通信。我刚开始接触这个模块时,觉得它不就是个简化版的 Wi-Fi 吗?后来踩了坑才明白,P2P 的复杂性全藏在状态机里。

今天我们就来拆解 Android 的 P2P 实现。核心有三个东西:WifiP2pServiceP2P 状态机、以及设备发现与连接的流程。

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,除非硬件支持并发。

关键点:WifiP2pService 通过 HIDL 接口与 wpa_supplicant 通信。Android 8.0 之后,这个接口变成了稳定的 HAL 层。

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 值。你想想看,这种问题如果不理解状态机,根本无从下手。

调试技巧:打开 logcat,过滤 "WifiP2pStateMachine",可以看到状态切换的完整日志。这是排查 P2P 问题的第一利器。

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 连接过程:从发现到握手

发现设备后,下一步就是连接。连接过程分为三个阶段:

  1. Provision Discovery:双方交换能力信息,比如支持的 WPS 类型、设备类型等。
  2. Group Negotiation:协商谁当 Group Owner(GO),谁当 Client。GO 的角色很重要,它负责管理整个 P2P 群组。
  3. 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 到建立连接的全过程。

P2P 设备发现与连接核心流程 应用层调用 discoverPeers() 应用层调用 connect() 应用层接收 广播回调 WifiP2pService P2P_FIND 命令 WifiP2pService P2P_CONNECT 命令 WifiP2pService 状态机切换 wpa_supplicant Probe Request/Response wpa_supplicant Provision + Group Negotiation wpa_supplicant WPS 握手 设备发现完成 PEERS_CHANGED 群组协商完成 GO/Client 确定 连接建立 CONNECTION_CHANGED 应用层 Framework wpa_supplicant 结果回调

从图中可以看到,整个流程是分层递进的。每一层只关心自己的职责,上层不关心下层的实现细节。这也是 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