3、WifiController 状态机:状态机设计模式、WifiController 的 7 种状态、状态切换逻辑

好,我们接着聊 WifiController。上一章我们把 WifiStateMachine 的内部状态梳理了一遍,那 WifiController 又是什么?

简单说,WifiController 是 Wi-Fi 开关的总闸。它不关心你连上了哪个 AP、信号强不强,它只关心一件事:Wi-Fi 功能现在是开着的,还是关着的?

我个人习惯把 WifiController 比作「房子的总电闸」,而 WifiStateMachine 是「房间里各个电器的开关」。总电闸没合上,房间里灯再亮也没用。

3.1 状态机设计模式:为什么 Android 这么爱用?

你想想看,Wi-Fi 的开关状态切换,其实是一个典型的有限状态机问题。用户点一下开关,系统要判断当前在什么状态,然后决定下一步做什么。

用 if-else 写当然也行,但 Wi-Fi 服务涉及异步消息、超时、外部事件(比如飞行模式、热点开启),状态一多,if-else 就变成一团乱麻。

状态机设计模式的核心思想就三个:

  • 状态:系统当前处于什么阶段
  • 事件:发生了什么(用户操作、系统广播、超时)
  • 转移:当前状态 + 事件 → 下一个状态

Android 的 StateMachine 框架把这三件事封装得很好。你只需要继承 State,重写 processMessage(),框架自动帮你做状态切换。

我的经验: 我在项目中遇到过有人自己手写状态机,用枚举 + switch-case。小项目还行,一旦状态超过 5 个,消息超过 10 种,代码就变得极难维护。Android 官方的 StateMachine 虽然有点重,但胜在稳定、可追溯。

3.2 WifiController 的 7 种状态

WifiController 定义了 7 种状态,我按「从关到开」的顺序列出来:

状态名 含义 典型触发事件
ApStaDisabledState Wi-Fi 和热点都关闭 开机、用户关闭 Wi-Fi
StaDisabledWithScanState Wi-Fi 关闭,但允许扫描 位置服务请求扫描
DeviceActiveState Wi-Fi 开启,正常工作 用户打开 Wi-Fi
ConnectivityState 已连接或正在连接 扫描到网络后自动连接
ScanOnlyState 仅扫描,不连接 设置中开启「仅扫描」模式
SoftApState 热点模式 用户开启热点
EcmState 以太网共享模式 USB 网络共享

嗯,这里要注意:这 7 种状态不是平级的。它们有父子层级关系。比如 DeviceActiveState 是父状态,ConnectivityStateScanOnlyState 是它的子状态。

为什么要这么设计?因为父状态可以处理「公共事件」。比如飞行模式开启时,不管你在 ConnectivityState 还是 ScanOnlyState,父状态 DeviceActiveState 统一拦截处理,子状态不用重复写代码。

3.3 状态切换逻辑:一张图说清楚

我画了一张状态切换图,你看完就明白了:

WifiController 状态切换逻辑 ApStaDisabledState StaDisabledWithScanState DeviceActiveState (父状态) ConnectivityState ScanOnlyState SoftApState EcmState CMD_SCAN CMD_WIFI_TOGGLED CMD_WIFI_TOGGLED CMD_WIFI_TOGGLED CMD_AP_START CMD_AP_STOP 自动连接 CMD_SCAN_ONLY CMD_SCAN_ONLY CMD_CONNECT USB 共享 USB 断开 开启/进入 关闭/退出 子状态切换

3.4 核心切换逻辑源码解读

我们来看一段 WifiController 中 ApStaDisabledState 的处理逻辑:

class ApStaDisabledState extends State {
    @Override
    public boolean processMessage(Message msg) {
        switch (msg.what) {
            case WifiController.CMD_WIFI_TOGGLED:
                // 用户打开了 Wi-Fi
                transitionTo(mDeviceActiveState);
                return HANDLED;
            case WifiController.CMD_SCAN:
                // 位置服务请求扫描
                transitionTo(mStaDisabledWithScanState);
                return HANDLED;
            case WifiController.CMD_AP_START:
                // 用户开启热点
                transitionTo(mSoftApState);
                return HANDLED;
            default:
                return NOT_HANDLED;
        }
    }
}

这段代码很直白。收到 CMD_WIFI_TOGGLED 就去 DeviceActiveState,收到 CMD_SCAN 就去 StaDisabledWithScanState

但有个细节:为什么热点开启不经过 DeviceActiveState? 因为热点和 Wi-Fi 不能同时开(硬件限制)。所以从 ApStaDisabledState 直接跳到 SoftApState,跳过了 Wi-Fi 的初始化流程。

我曾经踩过的坑: 有一次我在定制 ROM 时,想在热点开启的同时保留 Wi-Fi 扫描功能。结果发现 WifiController 的状态机设计不允许这种「并发」状态。后来我不得不修改状态机,新增了一个 SoftApWithScanState。这让我深刻体会到:状态机的设计决定了系统的能力边界

3.5 状态切换的「隐形规则」

除了显式的 transitionTo(),WifiController 还有几条隐形的切换规则:

  • 飞行模式:不管当前在哪个状态,收到飞行模式广播,一律回到 ApStaDisabledState
  • Wi-Fi 开关冲突:如果当前在 SoftApState,用户打开 Wi-Fi,系统会先关闭热点,再进入 DeviceActiveState
  • 超时保护:某些状态(如连接中)如果长时间没有进展,会回退到上一个稳定状态

这些规则在代码里分散在各个状态的处理逻辑中。我建议你阅读源码时,重点关注 processMessage() 中那些 NOT_HANDLED 的分支——它们往往会被父状态或默认状态拦截处理。

3.6 小结

WifiController 的状态机,说白了就是一套「Wi-Fi 开关的有限状态机」。7 种状态覆盖了 Wi-Fi 的所有工作模式,父子状态的设计减少了重复代码。

我个人觉得,理解 WifiController 的关键不在于记住每个状态的名字,而在于理解状态切换的触发条件和约束。比如「为什么热点和 Wi-Fi 不能共存」「为什么扫描状态可以独立存在」——这些问题的答案,都在状态机的设计里。

一句话总结: WifiController 是 Wi-Fi 功能的「总开关」,它用 7 种状态和清晰的切换逻辑,保证了 Wi-Fi 功能在各种场景下的正确性和稳定性。

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