资讯动态

Android WiFi关闭流程源码深度解析:从WifiManager到HAL层

发布时间:2026/10/6 3:48:00 来源:尧图企业网站定制
做Android系统开发的同学对WiFi的开启流程应该都不陌生网上相关的源码分析文章也不少。但“关闭”这个方向能说清楚的文章就少多了。其实做系统定制、做车机、做企业设备管理的时候关闭流程比开启流程更容易遇到奇怪问题——比如明明调用了WifiManager.setWifiEnabled(false)通知栏图标迟迟不变或者关闭后系统还在偷偷扫描再或者跟热点、飞行模式一叠加整个WiFi状态直接卡住。这篇文章就基于我实际跟过的代码把从上层WifiManager一路到HAL的关闭链路完整拆一遍把我自己踩过的坑也一并交代清楚。需要提前说明的是Android WiFi的代码在不同版本里变化非常大我这里的分析以Android 10之后、Android 14之前的主流架构为准。这个区间内WiFi模块已经完成了从WifiStateMachine一家独大到WifiControllerWifiStateMachineWifiNativeWifiVendorHal分层协作的演进关闭流程的主体框架是稳定的。太老的Android 8/9和更新的Android 15WiFi相关代码向模块化迁移更大我会在关键差异处单独标注。1. 关闭请求的入口从 WifiManager 到 WifiServiceImpl1.1 应用层调用与Binder链路大多数上层应用关闭WiFi调用的都是这一行WifiManager wifiManager context.getSystemService(WifiManager.class); boolean result wifiManager.setWifiEnabled(false);这个接口看起来简单但从应用进程到系统进程一共跨越了两次Binder调用。WifiManager本身是应用框架层的代理类真正的逻辑在系统进程的WifiServiceImpl里。WifiManager.setWifiEnabled的代码大致是Override public boolean setWifiEnabled(boolean enabled) { try { return mService.setWifiEnabled(mPackageName, enabled); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }这里的mService是IWifiManager的Binder代理接口实现在系统Server端的WifiServiceImpl。第一次Binder调用到这里就结束了WifiServiceImpl会做权限检查、前置状态校验然后真正的工作通过消息机制交给WiFi内部的状态机去执行。这里有个很多初学者会忽略的点setWifiEnabled返回值只代表“请求被系统接受了”不代表WiFi已经关掉了。系统真正完成关闭动作需要几百毫秒甚至几秒中间涉及Supplicant退出、驱动停止、网络注销等一系列操作。如果你在上层业务里用这个返回值判断“WiFi已经关了”那大概率会踩坑。1.2 WifiServiceImpl 的权限检查与前置条件WifiServiceImpl.setWifiEnabled的完整逻辑比很多人想象的复杂。它不仅仅是发个消息而是有一连串的检查。关键代码逻辑如下public boolean setWifiEnabled(String packageName, boolean enable) { if (enforceChangePermission(packageName) ! MODE_ALLOWED) { return false; } if (mAirplaneModeObserver.isAirplaneModeOn()) { // 飞行模式打开时直接拒绝或记录 } if (!mSettingsStore.isWifiToggleEnabled() enable) { // 更新用户Toggle设置 mSettingsStore.setWifiToggleState(enable); } mWifiController.sendMessage(CMD_WIFI_TOGGLED); return true; }第一个重点是权限检查。setWifiEnabled需要CHANGE_WIFI_STATE权限但实际执行时还要区分调用方身份。系统应用、预置应用和普通第三方应用的待遇完全不同。尤其从Android 13开始setWifiEnabled对第三方应用的限制大幅收紧普通应用在后台或者targetSdk较高时调用几乎必然被拒绝。如果你在处理系统级App或者车机方案这块要特别注意——很多设备厂商在定制时都会在这里打补丁把某些白名单应用放行。第二个重点是飞行模式。飞行模式下WiFi控制器会进入受限状态此时即使来了CMD_WIFI_TOGGLED消息WifiController内部也会根据飞行模式的开关状态决定是否真正执行关闭动作。早期Android版本里存在飞行模式和WiFi状态互相干扰的bug就是因为在WifiController里对这两个状态的处理不够严谨。第三个重点是mSettingsStore.setWifiToggleState。这行代码写入了用户的WiFi开关偏好也就是设置里那个“WiFi”开关的持久化状态。注意这里的逻辑是只有从关到开的时候才写入从开到关则不写。这个设计初看反直觉其实是因为Android要区分“用户主动关闭”和“系统临时停用”两种情况。比如短按飞行模式触发的WiFi关闭就不应该覆盖用户之前设置“WiFi保持开启”的偏好。2. WifiController 状态机整个关闭过程的“调度中枢”如果你跟过WiFi源码一定会发现整个WiFi框架里状态机无处不在。WifiController是WiFi模块的顶层状态机负责协调WiFi开关、热点、P2P、扫描等大方向的行为。关闭流程的主要调度就发生在这里。2.1 CMD_WIFI_TOGGLED 消息的流转WifiServiceImpl.setWifiEnabled发出去的CMD_WIFI_TOGGLED消息最终会被WifiController的处理线程收走。WifiController本身继承自StateMachine默认运行在“WiFi服务线程”上。消息收进来后会根据当前所处的状态决定如何处理。WifiController的几个核心状态包括StaEnabledStateWiFi完全开启、StaDisabledWithScanStateWiFi关闭但允许扫描、StaDisabledStateWiFi完全关闭、ApEnabledState热点开启、EcmState紧急呼叫模式等。不同状态的processMessage里对CMD_WIFI_TOGGLED的处理逻辑完全不同。这里最核心的一点是关闭WiFi这个动作最终要由StaEnabledState自己来裁定。也就是说WiFi当前处于开启状态收到关闭指令后StaEnabledState需要决定自己接下来转到哪个状态。代码逻辑大致如下class StaEnabledState extends State { Override public boolean processMessage(Message msg) { switch (msg.what) { case CMD_WIFI_TOGGLED: if (mSettingsStore.isWifiToggleEnabled()) { // 用户偏好显示WiFi仍要开启不处理 } else if (mSettingsStore.isScanAlwaysAvailable()) { transitionTo(mStaDisabledWithScanState); } else { transitionTo(mDisabledState); } break; ... } } }看到没有“关闭WiFi”在状态机层面其实是一个状态迁移动作。CMD_WIFI_TOGGLED消息本身只是触发器真正决定去哪个状态的是当前用户偏好和系统策略的组合。2.2 EnabledState → DisablingState → DisabledState从StaEnabledState迁出后WifiController会先进入DisablingState。这是一个中间过渡状态它的enter()就干了一件事通知WifiStateMachine停止Supplicant。class DisablingState extends State { Override public void enter() { mWifiStateMachine.setSupplicantRunning(false); } Override public boolean processMessage(Message msg) { switch (msg.what) { case WifiStateMachine.WIFI_STATE_CHANGED: if (msg.arg1 WifiManager.WIFI_STATE_DISABLED) { transitionTo(mDisabledState); } break; } return HANDLED; } }这段代码值得多敲两行理解一下。DisablingState进入后立刻发起Supplicant停止请求然后自己不再做实质工作而是等WifiStateMachine上报WIFI_STATE_CHANGED事件。只有当WifiStateMachine那边确认WiFi已经完全关闭WIFI_STATE_DISABLEDDisablingState才会带着这个结果迁入DisabledState。这样设计的好处是状态机之间的解耦非常干净WifiController只管“什么时候关”、“关完进哪个状态”真正的关闭执行细节全在WifiStateMachine和它下游的WifiNative里上层不关心。坏处就是排查问题的时候链路很长一个问题要跨三四个模块追。2.3 ScanAlwaysAvailable 带来的分支如果你的设备开启了“随时可扫描”scan_always_available关闭流程会走另一条分支——进入StaDisabledWithScanState而不是DisabledState。这个状态很特殊WiFi整体是关着的用户也看到图标是灰的但底层扫描功能仍然工作。class StaDisabledWithScanState extends State { Override public void enter() { mWifiStateMachine.setOperationalMode(ScanOnlyMode); mWifiStateMachine.setSupplicantRunning(true); } }注意这里setSupplicantRunning(true)又被调用了。也就是说在这种场景下Supplicant不是被停止而是被降级为“只扫描不连接”的模式。很多厂商在定制系统时会把这个功能关掉因为用户经常反馈“WiFi关了还在被扫描”隐私上说不清楚。但从系统框架角度讲这个设计是为了支持基于WiFi扫描的位置服务——Andoid的位置服务依赖WiFi扫描结果即使WiFi关闭系统也可以只扫描不关联。在StaDisabledWithScanState下用户虽然不能连接任何WiFi但系统可以持续拿到周围的BSSID列表用于定位。如果你的产品对功耗或者隐私有要求关闭这个特性的落点就在WifiController的StaEnabledState处理逻辑里把对mSettingsStore.isScanAlwaysAvailable()的判断强行置为false即可。3. 真正的断连WifiNative 与 HAL 层做了什么进入DisablingState之后关闭动作从“调度”进入了“执行”。所有脏活累活都在WifiStateMachine和WifiNative这一层完成。3.1 WifiStateMachine 的 setSupplicantRunning(false)WifiStateMachine收到CMD_SET_SUPPLICANT_RUNNING消息并携带false参数时会进入SupplicantStoppingState。这个状态做的事情很直白让WifiNative把Supplicant停掉。但停掉之前WifiStateMachine要先做一系列连接清理工作。第一个清理的是网络连接。如果当前WiFi已经连上某个APWifiStateMachine需要先把这条连接断开触发L2ConnectedState退出回到DisconnectedState。如果当前正在做DHCP或者正在认证也要分别做超时取消和认证中断。第二个清理的是底层接口。WifiStateMachine会调用mWifiNative.teardownInterface()把WiFi网络接口一般是wlan0从框架层摘除。这个动作直接对接到Vendor HAL。这段逻辑在排查问题时要特别留意Supplicant的停止和接口的teardown是两件独立的事但正常情况下它们必须按顺序完成。在我处理过的一个车机项目里vendor驱动对teardown接口的响应特别慢导致WifiStateMachine一直卡在SupplicantStoppingState最后上层超时整个WiFi服务被重启了一遍。3.2 teardownInterface 与 driver/supplicant 的退出WifiNative.teardownInterface()是Java层和Native层的一个关键分水岭。到这一步调用链开始进入HAL层。public void teardownInterface() { if (mIfaceIsUp) { mWifiVendorHal.teardownIface(mClientInterfaceName, mIfaceType); mIfaceIsUp false; } mWifiSupplicantControl.teardownSupplicantIfNecessary(); mWifiCondControl.teardownInterfaces(); }先看mWifiVendorHal.teardownIface。WifiVendorHal是WiFi HAL的封装层Android 10之后通过HIDL新版是AIDL和wifi.hal服务通信。teardownIface底层调用的是IWifi.stop()或者按接口实例执行remove操作。这一步做完驱动层面的WiFi接口就被移除数据通路彻底断开。再往下是Supplicant。mWifiSupplicantControl.teardownSupplicantIfNecessary()做的事情是如果已经没有其他接口依赖Supplicant就让wpa_supplicant进程退出。注意这里“IfNecessary”的考量——Android里Supplicant不是只服务一个接口的它同时管着STA、P2P等。如果P2P接口还活着Supplicant就不能直接杀掉只能先把STA接口从Supplicant里摘掉。最后mWifiCondControl.teardownInterfaces()是清理wificond进程的。wificond是Android 8之后引入的独立进程负责处理NL80211相关的内核通信。关闭WiFi时它监听的接口事件也要一并移除否则内核侧还有残留的接口引用下次开启时可能出现“interface already exists”之类的错误。3.3 超时与清理机制看了上面这几步你可能已经意识到关闭WiFi不是一个瞬时动作而是一系列Native操作的组合。任何一个环节卡住都会导致状态机停滞。WifiStateMachine用了一个很原始但有效的机制来兜底——消息超时。SupplicantStoppingState在进入时会发送一个延迟消息sendMessageDelayed(SUPPLICANT_STOP_TIMEOUT, SUPPLICANT_STOP_TIMEOUT_MS);如果Supplicant停止流程超过预期时间通常是几秒超时消息就会被处理WifiStateMachine强制把状态推到WIFI_STATE_DISABLED。这个兜底保证了上层用户至少能看到WiFi图标熄灭但代价是底层可能残留异常状态比如Supplicant进程没杀掉、接口没释放干净等。这里有一个我在项目里真实遇到过的坑某个厂商的WiFi驱动在异常断电后HAL层重启了但驱动固件没有加载完整teardownIface调用后HAL返回成功但实际上接口的引用计数没减干净。结果就是WiFi能正常关闭一次但第二次开启时wlan0就起不来了log里报Supplicant start failed。最后是让vendor在HAL层做了接口存在性检查才解决。4. 系统级联动关闭WiFi不只是“自己的事”很多做上层应用的同学会有个误解关闭WiFi就是系统WiFi模块内部的事跟其他服务没关系。实际上WiFi关闭会牵动ConnectivityService、BatteryStatsService、LocationManager、SystemUI等一大票系统服务。任何一个环节联动出问题用户看到的就是“WiFi图标不消失”、“网络突然卡一下”这类诡异现象。4.1 ConnectivityService 与 NetworkAgent 的注销WifiStateMachine内部维护着一个NetworkAgent对象它负责向ConnectivityService注册WiFi网络。连接成功时NetworkAgent会把网络的能力、链路属性等同步给ConnectivityService关闭时则要把这个网络注销掉。网络注销的触发点在断开连接流程中大致调用链是mNetworkAgent.unregister();ConnectivityService收到NetworkAgent的注销后会做默认网络切换的评估。如果设备当前只有WiFi一个网络注销后默认网络变为空所有依赖网络的业务会立刻收到CONNECTIVITY_ACTION广播和NetworkCallback.onLost()回调。如果设备同时有蜂窝网络这里还会触发一次网络切换把数据链路从WiFi迁到蜂窝。这个切换过程如果出现竞争条件就会出现用户感知的“断网几秒钟”。排查这类问题有个经验抓log时不能只看WiFi相关tag还要看ConnectivityService的日志。很多WiFi关闭后的“疑似网络问题”根因其实在ConnectivityService的网络评估和切换逻辑里而不是WiFi模块本身。4.2 电池统计、通知栏与UI刷新WiFi关闭涉及的用户可见变化主要是通知栏图标。这个变化的链路是WifiStateMachine将状态置为WIFI_STATE_DISABLED后WifiServiceImpl会发送WIFI_STATE_CHANGED_ACTION广播SystemUI的WifiIcons相关代码收到广播后刷新图标。广播是异步的所以从“底层WiFi真正关闭”到“通知栏图标变化”中间存在一个时间窗口。这个窗口通常只有几十毫秒但如果SystemUI进程卡顿或者广播队列堆积用户就会看到WiFi已经断了但图标还是满格信号——这种问题上报到厂商那边十有八九最后定位到SystemUI的消息队列或者广播接收器的ANR上。再看看电池统计。WifiServiceImpl在状态变化时会调用BatteryStatsServicemBatteryStats.noteWifiOff();这行代码虽然只是记录一个统计点但它的作用很实际电量统计页面里“WiFi使用时间”的计算就依赖这个标记。如果你在定制ROM时绕过WifiController直接操作底层关闭WiFi比如某些省电策略直接kill了wpa_supplicant进程电池统计就会失准显示WiFi一直在开着耗电。4.3 热点、P2P、飞行模式的交互WiFi关闭流程跟热点和P2P的交互也是个重灾区。WifiController里StaEnabledState在处理CMD_WIFI_TOGGLED时会先看当前有没有其他模块占着WiFi资源。比如热点开着的时候框架层通常不允许直接关WiFi因为softap和sta共用物理网卡。WifiController会把请求排队或者直接忽略具体行为取决于vendor的实现。还有飞行模式。飞行模式打开的时候系统会强制关闭WiFi、蓝牙、蜂窝等所有无线通信。这个关闭动作不是走CMD_WIFI_TOGGLED而是通过WifiController的另一个消息CMD_AIRPLANE_TOGGLED来触发的。两条路径最终都会走到WifiStateMachine.setSupplicantRunning(false)但前置处理逻辑完全不同。我之前在一个平板上遇到过一个问题打开飞行模式再关闭WiFi就回不来了必须重启才恢复。查了几天代码最后发现是vendor的WiFi HAL在飞行模式关闭WiFi时没有释放一个内核锁导致后续wlan0接口无法重新创建。这个问题的排查难点在于从框架层看一切正常日志里状态机流转全部正确问题全在HAL层。所以排查WiFi关闭相关问题一定要把framework层和HAL层日志同时抓取对比着看。5. 从源码到实战常见关闭异常与排查思路源码看完了说点实际排查时能直接用上的东西。我把自己在多个项目里遇到的关闭流程问题归纳成几类每类给出排查路径和定位建议。5.1 关闭慢 / 卡在“正在关闭”最常被反馈的问题就是WiFi关不掉或者关了很久图标才灭。排查思路按调用链从下往上走。第一步先看log里WifiStateMachine的SupplicantStoppingState是否长时间不退出。如果是说明卡在Native层。这时候去看HAL层日志重点确认teardownIface有没有调用、返回结果是什么。大多数卡住的情况都是vendor HAL对stop()或removeIfaceInstance的响应异常。如果SupplicantStoppingState早就退出了但WifiController的DisablingState一直收不到WIFI_STATE_DISABLED消息问题可能在WifiServiceImpl的广播发送或者WifiStateMachine的状态上报逻辑上。这种场景下看wifi_state相关的状态变化日志基本就能定位到是哪个环节把状态更新吞掉了。还有一种常见的性能问题是WiFi关闭慢是因为网络栈在关之前还在做大量的DNS缓存清理、网络统计上报等工作。这部分工作在高通平台上尤其多。如果你不需要那么彻底的清理可以在WifiStateMachine里找到对应的清理逻辑做裁剪但这个改动要慎重会影响网络统计和诊断功能的准确性。5.2 关闭后仍能扫描 / 定位服务仍在用WiFi这是隐私敏感产品上经常被挑战的一个点。用户明明把WiFi关了但抓包发现设备还在发出Probe Request。这通常就是前文说的StaDisabledWithScanState。Android默认在WiFi关闭后是否允许扫描由Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE控制。但这里有个隐蔽的坑不是只有这个开关控制扫描行为。如果你的系统集成了Google定位服务GMS或者使用了高通的Location扩展即使这个开关关掉定位服务仍然可能通过WifiManager.startScan()的接口触发单次扫描。如果要彻底关闭“WiFi关闭后的所有扫描”只改Settings开关是不够的需要动WifiController让WiFi关闭后直接进入DisabledState而不是StaDisabledWithScanState。同时还要检查一下WifiStateMachine里有没有其他入口能触发扫描比如WifiConnectivityManager。这个管理器负责周期性的网络发现和连接恢复如果它还在运行即使状态机是DisabledState也可能会因为定时任务去拉WiFi状态间接触发底层扫描。在Android 12之后WifiConnectivityManager的行为受WifiTrafficPoller的影响情况更复杂一些。建议在定制时直接把这个管理器的enable()入口在WiFi关闭时禁用掉。5.3 setWifiEnabled 权限收紧与替代方案Android 13之前第三方应用只要持有CHANGE_WIFI_STATE权限就能调用setWifiEnabled。从Android 13开始这个接口对普通应用基本关上了门系统会检查调用方是否持有NETWORK_SETTINGS或MAINLINE_NETWORK_STACK这两个签名权限没有就直接返回false。如果你的业务是设备管理类App需要远程关闭WiFi现在官方推荐的替代方案是用DevicePolicyManager因为设备所有者App本身有setWifiDisabled的权限。如果是普通App要做“省电时关闭WiFi”更合适的做法是引导用户去系统设置里关或者申请ACCESS_WIFI_STATECHANGE_WIFI_STATE后在Android 12L以下设备上处理Android 13则要考虑走辅助功能或者设备管理员通道。从框架源码层面看WifiServiceImpl.setWifiEnabled的权限检查逻辑如下private int enforceChangePermission(String packageName) { if (mAppOps.noteOp(AppOpsManager.OP_CHANGE_WIFI_STATE, ...) ! MODE_ALLOWED) { return MODE_IGNORED; } if (checkCallerHasPermission(android.Manifest.permission.CHANGE_WIFI_STATE)) { return MODE_ALLOWED; } if (checkCallerHasPermission(android.Manifest.permission.NETWORK_SETTINGS)) { return MODE_ALLOWED; } return MODE_DENIED; }这里可以看到NETWORK_SETTINGS这个隐藏权限被当成了高优先级通行证。系统应用如果要在Android 13设备上继续用setWifiEnabled必须在Manifest里声明android:sharedUserIdandroid.uid.system虽然这个方法已经被标记废弃或者直接持有NETWORK_SETTINGS签名权限。5.4 关闭流程中的日志抓取要点最后给一份排查关闭问题的log抓取清单按效率排序日志分类关键tag主要看什么WiFi框架wifi.WifiController状态机迁移是否正常是否卡在DisablingStateWiFi状态机wifi.WifiStateMachineSupplicant停止、接口teardown、状态上报WiFi Nativewifi.WifiNative、wificondteardownInterface是否执行Supplicant是否退出HAL层wifi.hal、wifi_vendorteardownIface返回驱动响应时间系统服务ConnectivityServiceNetworkAgent注销、默认网络切换上层UISystemUI、WifiIcons图标刷新是否及时抓log的时候建议用logcat -b all把所有buffer都拉出来因为WiFi关闭问题往往涉及多个进程和多个buffer。只抓mainbuffer经常漏掉system和events里的关键信息。另外补充一个小技巧复现问题前先执行adb shell cmd wifi status记录一下当前WiFi状态复现后再执行一次对比状态机的变迁。这个命令在Android 10上非常有用比我见过很多开发者在代码里打一堆临时log效率高得多。我在多个项目里跟过WiFi关闭流程整体的感受是这套框架的清晰度在Android系统各个模块里算中等偏上分层合理、状态机逻辑严谨但正因为它跨的层太多很多问题从表面上看是指向南辕北辙的方向的。遇到WiFi关闭相关的bug别急着怀疑某一行代码先从WifiController的状态机入手顺着CMD_WIFI_TOGGLED的消息流一路捋下去把每个状态迁移的触发条件都确认一遍大多数问题都能在这个过程里露出原形。这也算是我在源码里泡了几年之后总结出来的最实用的方法论了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑