资讯动态

Android WiFi关闭流程源码深度拆解:从开关到驱动下电

发布时间:2026/10/6 9:00:47 来源:尧图企业网站定制
在Android系统里wifi关闭流程看上去就是开关点一下的事但如果你顺着源码往下追会发现这一小段路径牵涉到WifiManager、系统服务、状态机、HAL以及内核驱动。这篇继续写Android wifi源码分析系列的第二篇专门拆解关闭流程把从用户点击到射频下电的每一步讲清楚。这个系列适合正在接触framework层开发、做ROM定制、或者被WiFi相关的疑难bug折磨过的人。上一篇我们把WiFi开启时的启动链路过了一遍这一篇反过来看关闭为什么关闭不能像拔网线一样简单状态机在中间起了什么作用底层驱动的teardown是怎么收尾的。把这些看完以后再遇到“WiFi关不掉”“关了还费电”“关完立刻开就连不上”这类问题你至少知道该翻哪段代码、抓什么日志。1. 关闭WiFi一条被低估的调用链1.1 从开关到驱动的全链路很多人第一次看Android WiFi源码时会觉得关闭比开启简单。开启要扫描、要认证、要DHCP、要打分选网关闭看起来不就是把网卡down掉吗实际上关闭流程的复杂度并不低只是它走得快很多中间状态一闪而过如果不加日志很难感知。一条完整的关闭链路大致长这样用户点击系统设置里的WiFi开关或者应用调用WifiManager.setWifiEnabled(false)这个请求会通过Binder进入系统进程的WifiServiceImpl。WifiServiceImpl做权限校验然后把关闭指令封装成消息发给WifiController这个状态机。WifiController判断当前状态后再让ActiveModeWarden去停掉正在运行的ClientModeManager。这个Manager是当前WiFi客户端模式的管理者它内部会触发WifiStateMachine的退出逻辑包括断开当前连接、停用网络、通知网络栈清理路由和DNS信息。接下来才是底层的事WifiNative拆掉Supplicant接口SupplicantStaIfaceHal停止wpa_supplicantWifiVendorHal调用Vendor HAL移除网络接口最后通过内核的cfg80211让驱动把射频关掉。这一整条链路跨了Java和C两层涉及Binder、状态机消息、HAL IPC任何一环卡住都会表现成用户能感知到的异常。1.2 为什么必须引入状态机不少刚接触WiFi源码的人会有一个疑问关闭就是调用一下驱动的stop为什么Android要在中间加一个WifiController状态机直接用同步调用一路调到底不是更省事吗答案在于Android的WiFi模块不是只有“开”和“关”两个状态。它同时要处理热点模式、WiFi Direct、扫描请求、飞行模式、企业认证、网络打分等多个并发体系。比如用户在连接WiFi的过程中切到飞行模式或者热点正在开启的同时用户点了WiFi开关这些事件如果都直接并发去操作底层硬件驱动根本扛不住。状态机做了一件事把所有WiFi相关的事件排队同一个时刻只允许一个状态迁移发生。WifiController内部维护了StaDisabledState、StaEnabledState、StaActiveState等状态不同状态下对同一事件的响应逻辑不同。你在关闭WiFi时实际上是把状态从“启用中”迁移到“已停用”然后状态机的enter/exit方法里会执行相应的资源回收操作。这套设计保证快速连续点击开关、飞行模式切换、热点开关等竞态场景下底层不会收到互相矛盾的指令。1.3 不同Android版本的源码差异分析WiFi源码时版本差异是一个绕不开的坑。Android 9之前WifiStateMachine是绝对的大管家扫描、连接、关闭全在它内部做。Android 10开始Google对WiFi架构做了一次大拆分把状态机和具体的模式管理剥离开出现了ClientModeManager和ActiveModeWarden。到了Android 12左右WifiController被精简更多协调逻辑下沉到ActiveModeWarden里。我建议阅读时以Android 12或13的AOSP代码为基线版本太老的话类名和调用链对不上容易被带偏。下面这张表可以帮你快速对齐模块Android 8/9Android 10/11Android 12WiFi总控WifiControllerWifiControllerWifiController精简版模式管理WifiStateMachine直接管理ActiveModeWarden ClientModeManagerActiveModeWarden ClientModeManagerSupplicant访问WifiNative直接调SupplicantStaIfaceHalSupplicantStaIfaceHalVendor HALWifiHalWifiVendorHalWifiVendorHal后面所有分析我都会以较新的AOSP代码为准遇到老版本差异会单独标注。2. 应用层入口WifiManager到系统服务的调用细节2.1 谁在调用setWifiEnabled应用层关闭WiFi的标准入口是WifiManager.setWifiEnabled(false)。这个接口看起来简单但内部走的是Binder跨进程调用。// WifiManager.javaAOSP代码简化摘录 public boolean setWifiEnabled(boolean enabled) { try { return mService.setWifiEnabled(mContext.getOpPackageName(), enabled); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }mService是IWifiManager这个AIDL接口的代理真实实现在系统进程的WifiServiceImpl里。这里有一个细节传入的packageName是调用方的包名不是WifiManager自己的包名。这个包名非常重要系统要拿它来做权限校验和调用来源记录。打开系统设置的开发者选项里的“显示点按操作”或者直接看logcat你能看到WifiService打印类似这样的日志WifiService: setWifiEnabled packagecom.android.settings uid1000 enablefalse这一行是定位问题的金钥匙。遇到“WiFi莫名关闭”的情况第一件事就是过滤这个关键字看看是哪个应用在什么时间点发起了关闭请求。2.2 WifiServiceImpl做了什么WifiServiceImpl拿到关闭请求后不是马上去操作硬件而是先做三件事权限校验、状态记录、消息分发。// WifiServiceImpl.javaAOSP代码简化摘录 Override public boolean setWifiEnabled(String packageName, boolean enable) { enforceChangePermission(); // ... mSettingsStore.handleWifiToggled(enable); mWifiController.sendMessage(CMD_WIFI_TOGGLED, enable ? 1 : 0); return true; }权限校验走的是enforceChangePermission检查调用方是否持有CHANGE_WIFI_STATE权限。没有权限直接抛SecurityException这个异常会上抛到调用方。接着mSettingsStore.handleWifiToggled(enable)会把开关状态持久化到SettingsProvider。注意这里有个容易忽略的点用户点关闭开关时系统先把“希望WiFi关闭”这个意图存入设置数据库然后再发消息给状态机。状态机处理完并真正关闭硬件后Settings里wifi_on的值才是最终生效的状态。如果后续某一步失败可能出现UI上开关已经关闭了但WiFi实际还开着的情况。最后通过mWifiController.sendMessage(CMD_WIFI_TOGGLED, enable ? 1 : 0)把关闭指令发送到状态机。sendMessage是异步的调用线程一般是Binder线程不会被阻塞真正干活的是WifiController所在的handler线程。这也是为什么你不能在测试代码里连续调用setWifiEnabled然后立刻读状态因为关闭流程还在状态机消息队列里排队没执行完。2.3 返回值的真相setWifiEnabled返回一个boolean表示请求是否被接受。这里有一点迷惑性这个返回值不代表WiFi已经成功关闭了只表示请求递交给系统服务时没有出现异常。真正的关闭成功与否需要通过注册WifiManager.WIFI_STATE_CHANGED_ACTION广播或者轮询getWifiState()来监听。WiFi状态会从WIFI_STATE_ENABLED变成WIFI_STATE_DISABLING最后变成WIFI_STATE_DISABLED。如果卡在DISABLING状态超过几秒说明关闭流程内部有问题多半是底层HAL调用没有返回。WifiStateMachine: setWifiEnabled false - disconnect WifiStateMachine: setWifiEnabled false - supplicant stop如果看到日志停在supplicant stop之后没有后续了优先怀疑Supplicant进程没有正常退出。3. 核心协调者WifiController与ActiveModeWarden3.1 WifiController状态机如何响应关闭WifiController是WiFi关闭流程的决策者。它接收到CMD_WIFI_TOGGLED消息后根据当前状态决定要不要执行关闭。在StaEnabledState状态下收到关闭指令后// WifiController.javaAOSP代码简化摘录 private class StaEnabledState extends State { Override public boolean processMessage(Message msg) { switch (msg.what) { case CMD_WIFI_TOGGLED: if (!mSettingsStore.isWifiToggleEnabled()) { mActiveModeWarden.shutdownClientModeManagers(); transitionTo(mStaDisabledState); } return HANDLED; // ... } return NOT_HANDLED; } }注意这里判断关闭条件用的不是消息里的enable参数而是重新读了一次Settings里的值isWifiToggleEnabled()。这样做是为了避免消息参数和持久化状态不一致。如果用户连续快速点击开关最后一次点击的意图会被记录到Settings里状态机以Settings为准而不是按照消息队列里的顺序执行。理解了这一点你就能明白为什么快速点击开关时WiFi状态最终总是和UI开关保持一致而不是卡在中间。transitionTo(mStaDisabledState)是状态迁移的关键。这个调用会让当前状态的exit方法执行清理逻辑然后进入StaDisabledState执行新状态的enter方法。很多资源释放动作就藏在这些enter/exit方法里比如取消延迟消息、停止WifiNotificationController、关闭wifi watchdog等。3.2 ActiveModeWarden如何回收ClientModeManagerWifiController本身不直接操作具体网络模式真正干活的是ActiveModeWarden。shutdownClientModeManagers()会遍历当前所有处于活动状态的ClientModeManager并逐个调用stop()。在AOSP较新版本中ClientModeManager的类型是ClientModeImpl它负责管理WiFi客户端模式下所有生命周期事件包括state machine的启动与停止。stop流程大致如下// ClientModeImpl.javaAOSP代码简化摘录 private void stop() { // 标记当前模式正在停止 mWifiStateMachine.terminateClientMode(); // 通知框架层WiFi已断开 // 移除interface }WifiStateMachine.terminateClientMode()内部会发送CMD_DISCONNECT断开当前所有连接清理网络评分缓存停止扫描调度。如果是车载或企业环境下还涉及802.1x认证状态的清理。这里有个常见的耗时代码点如果当前WiFi连接刚好在认证阶段驱动或Supplicant的响应慢了断开操作会等待一段时间。表现出来就是用户点了关闭但状态一直停在“正在关闭”几秒钟。3.3 状态机迁移过程中的边界情况关闭流程中比较棘手的是各种边界情况。第一种是WiFi关闭时刚好有应用正在请求扫描。Android的扫描请求是有计数机制的关闭流程会调用WifiScanner和WifiScanningService的清理接口把所有扫描请求置为失败并回调给上层应用。如果这个过程没做好应用会一直等扫描结果表现就是WiFi关了以后某些应用一直转圈。第二种是热点和WiFi客户端共存的场景。部分设备支持WiFi客户端和热点同时开关闭客户端模式时只要热点还开着底层射频可能不会完全关闭。这时候从用户角度看WiFi开关已经关了但设备WiFi热点还在广播不算bug但在功耗分析时要特别注意。第三种是飞行模式叠加关闭。飞行模式下系统会同时关闭WiFi、蓝牙和移动网络事件来源不同但最终都会汇聚到WifiController。WifiController会通过消息优先级和内部状态判断避免重复执行关闭逻辑导致底层收到两次teardown。4. 底层关闭Supplicant、Vendor HAL与驱动4.1 WifiNative的teardown逻辑状态机层面处理完业务逻辑后接下来是WifiNative负责的底层拆解。WifiNative是Java层和C底层之间的桥接它聚合了Supplicant和Vendor HAL两个通道。关闭时主要做两件事停止Supplicant接口的通信移除Vendor HAL里的网络接口。// WifiNative.javaAOSP代码简化摘录 public void teardownInterface(String ifaceName) { if (!mSupplicantStaIfaceHal.teardownIface(ifaceName)) { Log.e(TAG, Failed to teardown iface ifaceName); } if (!mWifiVendorHal.removeIface(ifaceName)) { Log.e(TAG, Failed to remove iface ifaceName); } }SupplicantStaIfaceHal.teardownIface会让wpa_supplicant释放对应的网络接口状态断开与AP之间的802.11管理连接清理PMK缓存。这一步完成后wpa_supplicant进程可能仍然存活但对应接口已经不再工作。WifiVendorHal.removeIface调用Vendor HAL的IWifiChip.removeIface告诉底层固件这个STA接口要被销毁。Vendor HAL的实现因芯片厂商而异高通和MTK的代码路径完全不同但对外表现一致接口被移除后上层通过ifconfig看到wlan0已经不存在了。4.2 内核驱动与射频下电HAL层移除接口后驱动层面会触发一系列内核操作。具体来说Vendor HAL的removeIface最终会调用内核的cfg80211接口下发cfg80211_disconnect和网络设备unregister操作。驱动收到通知后停止该网卡的TX/RX队列释放DMA缓冲区停止硬件定时器最后关闭射频前端。这里要理解一个概念Android的WiFi射频通常是和蓝牙共存的Combo芯片。关闭WiFi客户端模式时如果蓝牙还在扫描或连接射频前端不会被完全关断因为蓝牙需要继续使用2.4G频段。所以你在功耗测试里看到WiFi关闭后仍有少量电流很有可能是蓝牙在保活不一定是WiFi的锅。驱动是否真正下电有一个简单的验证方法adb shell cat /sys/class/net/wlan0/phy80211/name关机状态下这个路径应该不存在或不可读。如果路径还在且能读到phy信息说明驱动层没有完全卸载只是上层逻辑停了。4.3 用日志完整追踪一次关闭实际操作中追踪关闭流程最有效的方式是过滤关键TAG的logcat。adb logcat -v threadtime -s \ WifiService:* \ WifiController:* \ WifiStateMachine:* \ ClientModeImpl:* \ WifiNative:* \ SupplicantStaIfaceHal:* \ WifiVendorHal:*一次正常关闭的日志顺序大约是这样WifiService: setWifiEnabled packagecom.android.settings uid1000 enablefalse WifiController: CMD_WIFI_TOGGLED - enabledfalse ActiveModeWarden: shutdownClientModeManagers ClientModeImpl: setWifiEnabled - stop WifiStateMachine: disconnect command received WifiNative: teardownInterface wlan0 SupplicantStaIfaceHal: teardownIface wlan0 WifiVendorHal: removeIface wlan0如果日志缺失了某一步或者卡在某一步迟迟没有下一个TAG输出基本就能锁定问题所在。比如卡在WifiStateMachine: disconnect command received之后不动优先查上层有没有应用持有WifiLock导致断开流程被挂起。卡在SupplicantStaIfaceHal: teardownIface之后优先查wpa_supplicant进程状态和SELinux权限。需要说明的是不同Android版本里TAG名称会有差异老版本是WifiStateMachine为主新版本是ClientModeImpl为主抓日志时两个TAG都过滤上比较保险。5. 关闭流程常见问题与排查技巧5.1 现象一关闭后立刻开启连不上这是我在论坛里见到最多的问题之一。用户反馈WiFi关闭后马上重新打开设备会扫描不到网络或者连接不上要等十几秒甚至重启WiFi才能恢复。从源码角度分析这个问题的根源通常是关闭流程没有完全走完supplicant或者driver还停留在半清理状态。比如teardownIface返回失败但上层忽略了错误或者驱动固件还在处理旧的断开流程新的开启请求就到了。机制上类似你正在拔U盘却没等系统提示“安全删除硬件”就立刻插回去设备枚举会失败。排查思路分三步。第一步看日志确认关闭流程是否完整走到WifiVendorHal: removeIface。第二步看supplicant进程是否存在残留执行adb shell ps -A | grep wpa正常情况下关闭完成后这个进程应该消失或者不再绑定任何接口。第三步看驱动状态adb shell dmesg | grep wlan可以查看驱动层报错。如果问题能稳定复现可以在关闭完成后主动加上500ms到1s的延时再开启看问题是否消失。如果是说明驱动固件本身就没有处理好快速重启这时候只能从Vendor HAL层面规避。5.2 现象二WiFi关了但功耗还是高很多做设备的同学会碰到这种情况WiFi开关已经显示关闭整机待机电流还是偏高。排除屏幕和移动网络的因素后WiFi模块的热点扫描和位置扫描是重点怀疑对象。Android从6.0开始WiFi扫描不只是WiFi开关控制还受Location服务控制。如果应用请求了定位权限并调用了WifiManager.startScan()即使WiFi关闭系统也可能为了定位保持WiFi射频在低功耗扫描模式下工作。检查方法是通过dumpsysadb shell dumpsys wifi | grep -iE scan|request|wakeup重点看ScanRequestProxy中的活跃扫描请求以及WifiWakeupController的状态。如果存在高频扫描请求再往上定位是哪个应用通过WifiManager或WifiScanner发起的。用adb shell cmd activity get-uid-state结合日志可以进一步判断。另外还有一类设备特有的问题某些第三方WiFi模块比如部分PCIE接口的Realtek RTL8852BE网卡在关闭WiFi时如果蓝牙共存没有处理好射频发射链路不会被完全关断导致持续漏电。这类问题日志层面看着一切正常必须抓kmsg和电流曲线才能定位。5.3 现象三第三方设备与随身WiFi的坑现在市面上的随身WiFi、智能网关很多是基于Android系统开发的它们对WiFi的定制程度很高有些设备甚至直接砍掉了标准的WifiService框架改用厂商私有的WiFi方案。以ufi001c这类常见的随身WiFi主板为例它的WiFi模块可能不是标准的SDIO或PCIE接口而是通过USB连接的外部模组。这时候系统里的WifiNative链路和标准手机完全不同可能整个关闭流程都在一个厂商私有驱动里完成根本不走AOSP的SupplicantStaIfaceHal逻辑。排查这类设备时标准化日志过滤不一定有效要优先抓设备本身的串口日志和内核日志。另外随身WiFi经常把WiFi热点常驻作为卖点它所谓的“关闭WiFi”可能只是停止了AP模式射频还在为基站通信工作。所以做功耗和功能测试前先确认产品定义的“关闭”到底关的是哪一层。5.4 抓一份有效log的正确姿势最后说一下抓log的姿势。很多人遇到WiFi问题就随手adb logcat抓一段结果拿到的日志要么缺上下文要么权限不够看不到关键TAG白白浪费排查时间。正确做法是先把日志缓存调大然后完整复现问题。推荐用如下命令adb logcat -b all -v threadtime wifi_close_issue.log抓取范围要覆盖问题发生前30秒到发生后30秒。比如你复现“WiFi关了但功耗高”那就要在关闭前就开始抓直到确认WiFi完全关闭并观察功耗一段时间后再停。如果问题涉及底层驱动还需要并行抓取内核日志adb shell dmesg -w dmesg_wifi.log抓完后做一个文件归档一份logcat、一份dmesg、一份adb shell dumpsys wifi输出。这三件套已经能覆盖大部分framework层和驱动层问题。如果是功耗问题再加一份adb shell dumpsys batterystats和电流计曲线数据就完整了。我个人在实际操作中的一个体会是WiFi关闭流程的大部分疑难问题最后都能在日志里找到答案区别只是你有没有抓到关键的那几行。与其反复试猜测不如把上面的日志三件套养成习惯遇到问题先归档再动手。另外一个小技巧如果设备支持尽量在抓日志时用adb shell cmd wifi set-wifi-enabled disabled代替手动点击开关这样能排除UI层的干扰让问题更纯粹地暴露在framework和驱动层面。

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

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

免费获取报价 →
↑