4. WifiStateMachine 详解:状态机层次结构、Supplicant 交互、驱动加载与卸载

各位同学,今天我们来啃一块硬骨头——WifiStateMachine

说实话,我刚接触 Android Wi-Fi 时,最头疼的就是这个状态机。代码动辄几千行,状态嵌套状态,回调套回调。但后来我发现,搞懂它,你就掌握了 Wi-Fi 框架的命脉。

4.1 状态机设计哲学:为什么非要用状态机?

Wi-Fi 的工作流程不是线性的。你想想看:

  • 用户点击「打开 Wi-Fi」
  • 驱动要加载
  • Supplicant 要启动
  • 扫描要执行
  • 连接要处理
  • 断线要重连

这些操作互相交织,如果用 if-else 写,代码会变成一团乱麻。状态机的好处是:每个状态只关心自己该做的事,状态切换由框架自动完成。

核心思想:WifiStateMachine 继承自 StateMachine,内部维护了一个状态栈。消息进来后,从当前状态开始逐级向上传递,直到某个状态处理了它。

4.2 状态层次结构:一张图看懂

我个人习惯把 WifiStateMachine 的状态分为三层:

DefaultState ConnectModeState DisconnectedState ScanState ConnectingState ObtainingIpState ConnectedState 根状态 一级子状态 二级子状态 三级子状态

嗯,这里要注意:状态是分层的。比如 ConnectModeState 处理连接相关的通用逻辑,而它的子状态只处理具体场景。消息如果被子状态消费了,就不会往上冒泡。

避坑指南:我曾经在定制 Wi-Fi 功能时,往错误的状态里发了消息,结果驱动加载了但 Supplicant 没启动。排查了半天才发现是状态没切过去。记住:发消息前先确认当前状态

4.3 Supplicant 交互:HIDL 与 Socket 的双通道

WifiStateMachine 和 wpa_supplicant 之间怎么通信?说白了就两条路:

  1. HIDL 接口(Android 8.0+):通过 IWifiStaIface.hal 调用,比如 startScan()、connect()。
  2. Socket 监听:wpa_supplicant 通过 UNIX socket 上报事件,比如 SCAN_RESULTS、CONNECTED、DISCONNECTED。

我建议你重点关注事件处理流程。举个例子:

// WifiMonitor 收到 supplicant 事件
private void handleSupplicantEvent(String event) {
    if (event.startsWith("CTRL-EVENT-CONNECTED")) {
        // 发送消息到状态机
        sendMessage(WifiStateMachine.CMD_SUPPLICANT_CONNECTED);
    } else if (event.startsWith("CTRL-EVENT-DISCONNECTED")) {
        sendMessage(WifiStateMachine.CMD_SUPPLICANT_DISCONNECTED);
    }
}

// 状态机处理
case CMD_SUPPLICANT_CONNECTED:
    transitionTo(mObtainingIpState);
    break;

你看,事件驱动状态切换,这就是状态机的精髓。

注意:Supplicant 事件是异步的。如果你在状态机里同步等待某个事件,系统可能会 ANR。我踩过这个坑——当时在 ConnectedState 里直接调了阻塞 API,结果 UI 线程卡死。后来改成异步回调 + 超时机制才解决。

4.4 驱动加载与卸载:从固件到内核

驱动加载这块,很多同学觉得离应用层很远。其实 WifiStateMachine 里就有触发点。

流程大致是这样的:

  • 用户打开 Wi-Fi → WifiServiceImpl 调用 WifiStateMachine 的 setWifiEnabled(true)
  • 状态机切换到 DriverLoadingState
  • 调用 WifiNative 的 loadDriver() → 通过 HIDL 调用内核模块
  • 内核加载固件(比如 wlan.ko)→ 初始化硬件
  • 启动 Supplicant → 状态机切换到 SupplicantStartingState
  • Supplicant 启动完成 → 切换到 DisconnectedState

卸载流程反过来:

case CMD_WIFI_TOGGLED:
    if (!enabled) {
        // 先断开连接
        transitionTo(mDisconnectingState);
        // 然后停止 Supplicant
        mWifiNative.stopSupplicant();
        // 最后卸载驱动
        mWifiNative.unloadDriver();
        transitionTo(mInitialState);
    }
    break;

关键点:驱动加载和 Supplicant 启动是串行的。驱动没加载完,Supplicant 启动会失败。我曾经在低端设备上遇到过驱动加载超时,结果 Supplicant 一直起不来,Wi-Fi 开关打不开。后来加了重试机制才稳定。

4.5 实战:如何调试状态机?

调试 WifiStateMachine 其实有套路。我个人常用三板斧:

方法 命令/工具 说明
查看当前状态 dumpsys wifi | grep "mStateMachine" 直接看状态机当前在哪个状态
跟踪消息 logcat -s WifiStateMachine 观察消息流转,看哪个状态处理了消息
模拟事件 adb shell cmd wifi send-trigger 手动触发扫描、连接等事件

举个例子,你发现 Wi-Fi 连不上,先跑 dumpsys wifi 看看状态机是不是卡在 ObtainingIpState。如果是,那问题大概率在 DHCP 上,而不是 Supplicant。

小技巧:我习惯在关键状态切换处加自定义日志。比如在 transitionTo() 之前打印当前状态和目标状态。这样出问题时,日志一看就知道状态机走到哪了。

4.6 总结:状态机就是 Wi-Fi 的「大脑」

WifiStateMachine 把 Wi-Fi 的复杂流程抽象成了状态和消息。你只要记住三点:

  • 状态分层:父状态处理通用逻辑,子状态处理具体场景
  • 事件驱动:Supplicant 上报事件,状态机响应切换
  • 串行加载:驱动 → Supplicant → 连接,一步都不能乱

搞懂这些,你就能在 Wi-Fi 框架里自由穿梭了。下次遇到 Wi-Fi 问题,先问自己一句:「当前状态机在哪个状态?」答案往往就在那里。


公众号:蓝海资料掘金营,微信deep3321