你点了一下"WiFi"开关,屏幕上的图标转了两圈,WiFi 列表就出现了。这半秒钟里,Android 系统做了多少事?从 Java 框架的权限检查,到状态机的层层调度,到 HAL 进程的拉取、再到内核 netlink 通道的建立——本文从点击开关开始,一路追踪到 HAL 就绪那一刻,把整条链路的代码打开来看。
本章导读
本章比喻:WiFi 打开的过程,就像给一栋大楼供电。你按下墙上的开关(
WifiManager),电流不会直接到电器——它要先经过配电箱的断路器检查线路是否安全(WifiServiceImpl权限校验),再由总调度室决定启用哪条供电线路(ActiveModeWarden+WifiController状态机),然后逐级合上楼层配电柜的闸(ConcreteClientModeManager→ClientModeImpl),最后接通大楼与电网之间的变压器(HAL → nl80211 → kernel)。每一层都有自己的"保险丝"和"合闸顺序",跳过一个环节整栋楼都不会有电。
你将学到:
•Android WiFi 从 App 层到 HAL 就绪的完整调用链•
WifiServiceImpl的 6 道权限/场景闸门•ActiveModeWarden+WifiController的状态机驱动机制•ConcreteClientModeManager与ClientModeImpl的初始化流程•HalDeviceManager→ AIDL/HIDL → 厂商 .so 的加载链路•QCOM 与 MTK 厂商 HAL 的实现差异•nl80211 netlink 通道的建立方式
数据来源:
•packages/modules/Wifi[1](AOSP WiFi Framework,Java)•hardware/interfaces/wifi[2](AIDL HAL 默认实现,C++)•hardware/qcom/wlan/qcwcn/wifi_hal[3](高通 QCOM 厂商 HAL)•hardware/mediatek/wlan/wifi_hal[4](联发科 MTK 厂商 HAL)
代码说明:本文所有代码块均来自真实源码,有部分精简(去除 log 行、注释行),核心逻辑完整保留。文件路径标注在代码块首行注释。
一、点击 WiFi 开关后,第一行代码在哪里执行?
一句话总结:App 层的
WifiManager.setWifiEnabled()只是一个薄壳,真正的逻辑在系统进程system_server中的WifiServiceImpl。
1.1 架构全景图
这张图覆盖了从 App 层到内核的完整调用链,建议先花 30 秒建立整体印象,再逐层深入。

图中最关键的几条线:
•蓝色箭头 = 主调用流(跨层传递,跟随它就能走完整个链路)•灰色箭头 = 内部委托(同层组件间的方法调用)•绿色箭头 = Vendor HAL 到内核的 nl80211 通道
1.2 WifiManager.setWifiEnabled() — 它真的什么都没做
你在 Settings 里点击 WiFi 开关,最终会调用到 WifiManager.setWifiEnabled():
// packages/modules/Wifi/framework/java/android/net/wifi/WifiManager.java@Deprecatedpublic boolean setWifiEnabled(boolean enabled) {try {return mService.setWifiEnabled(mContext.getOpPackageName(), enabled);} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}
主要功能:
•方法体只有 5 行,全部逻辑就是委托给 mService(一个 IWifiManager 的 Binder 代理对象)•mContext.getOpPackageName() 传出调用方的包名,这是为了后面权限检查时能区分"谁在请求"•RemoteException 被 rethrowFromSystemServer() 包装成 RuntimeException 抛出,对 App 来说它是透明的•这不是一个异步方法——调用方会阻塞等待 system_server 的 Binder 响应
那 mService 这个对象是怎么来的?WifiManager 的构造函数接收一个 IWifiManager 参数,它是由 Context.getSystemService(WIFI_SERVICE) 通过 ServiceManager 查找到的 Binder 代理。IWifiManager 是一个 AIDL 接口,定义如下:
// packages_modules_Wifi/framework/java/android/net/wifi/IWifiManager.aidlboolean setWifiEnabled(String packageName, boolean enable);
从 App 进程到 system_server 的 Binder 调用,走的是 Android 标准的跨进程通信机制。本文不展开 Binder,你只需要知道:调用 mService.setWifiEnabled() 之后,执行权已经跳转到了 system_server 进程中的 WifiServiceImpl。
二、WifiServiceImpl 拿到请求后,做了什么检查?
一句话总结:
WifiServiceImpl.setWifiEnabled()的本质是配电箱里的断路器面板——它在启动 WiFi 之前逐项检查权限和场景约束,不满足条件的断路器会跳闸,直接切断后续供电链路。
回到我们的大楼供电比喻——如果说 WifiManager.setWifiEnabled() 是墙上的开关,那么 WifiServiceImpl.setWifiEnabled() 就是配电箱里的断路器面板。只有所有断路器都处于"允许"状态,电流才能往下游走。
2.1 权限检查:谁有资格打开 WiFi?
// service/java/com/android/server/wifi/WifiServiceImpl.java@CheckResultprivate int enforceChangePermission(String callingPackage) {mAppOps.checkPackage(Binder.getCallingUid(), callingPackage);if (checkNetworkSettingsPermission(Binder.getCallingPid(), Binder.getCallingUid())) {return MODE_ALLOWED;}mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE, "WifiService");return mAppOps.noteOp(AppOpsManager.OPSTR_CHANGE_WIFI_STATE,Binder.getCallingUid(), callingPackage);}
主要功能:
•先检查签名级权限 NETWORK_SETTINGS——持有此权限的系统 App(如 Settings)直接放行,无需后续检查•否则强制执行 CHANGE_WIFI_STATE 危险权限——没有的话直接抛 SecurityException•最后通过 AppOpsManager.noteOp() 记录本次权限使用,供审计追踪•注意这里的双路径设计:系统 App 走快捷通道(NETWORK_SETTINGS),普通 App 走完整通道(CHANGE_WIFI_STATE + AppOps)
checkNetworkSettingsPermission() 的实现也值得一看——它直接检查调用者的 PID/UID 是否持有 NETWORK_SETTINGS:
// service/java/com/android/server/wifi/WifiServiceImpl.javaprivate boolean checkNetworkSettingsPermission(int pid, int uid) {return mContext.checkPermission(android.Manifest.permission.NETWORK_SETTINGS, pid, uid)== PERMISSION_GRANTED;}
这是签名级(signature)权限,只有用平台签名签过的 App 才能持有——也就是说只有系统应用能拿到这个"绿色通道"。
2.2 场景闸门:什么情况下不能打开 WiFi?
权限过了之后,还有一连串场景检查。这就像断路器面板上不只有"权限"这一个开关,还有"飞行模式"、"卫星模式"、"热点正在用"等一系列互锁装置。看 setWifiEnabled() 的完整实现:
// service/java/com/android/server/wifi/WifiServiceImpl.java@Overridepublic synchronized boolean setWifiEnabled(String packageName, boolean enable) {if (enforceChangePermission(packageName) != MODE_ALLOWED) {return false;}enforceValidCallingUser();int callingUid = Binder.getCallingUid();int callingPid = Binder.getCallingPid();boolean isPrivileged = isPrivileged(callingPid, callingUid);boolean isThirdParty = !isPrivileged&& !isDeviceOrProfileOwner(callingUid, packageName)&& !mWifiPermissionsUtil.isSystem(packageName, callingUid);boolean isTargetSdkLessThanQ = mWifiPermissionsUtil.isTargetSdkLessThan(packageName, Build.VERSION_CODES.Q, callingUid) && !isGuestUser();mWifiPermissionsUtil.checkPackage(callingUid, packageName);// 闸门1:第三方App SDK版本检查if (isThirdParty && !isTargetSdkLessThanQ) {mLog.info("setWifiEnabled not allowed for uid=%").c(callingUid).flush();return false;}// 闸门2:卫星模式if (mSettingsStore.isSatelliteModeOn()) {mLog.info("setWifiEnabled not allowed as satellite mode is on.").flush();return false;}// 闸门3:飞行模式if (mSettingsStore.isAirplaneModeOn() && !isPrivileged) {mLog.err("setWifiEnabled in Airplane mode: only Settings can toggle wifi").flush();return false;}// ... 后续闸门见下段
前三个闸门关注的是"调用者有没有资格、设备状态允不允许":
•闸门 1 是 Android Q 引入的安全边界——第三方 App 在 Q+ 设备上不允许直接开关 WiFi,必须走系统提供的 Settings Panel API。这个设计的原因是:Q 之前第三方 App 可以静默关闭 WiFi 来干扰用户的网络连接,所以 Q 开始收紧了这一权限•闸门 2 是硬件资源共享冲突——卫星通信和 WiFi 共用射频前端,两者不能同时使用。这不是"不让你用",而是物理上只能二选一•闸门 3 是系统级快捷开关的保护——飞行模式下只有 Settings 能打开 WiFi(比如用户手动在飞行模式里单独打开 WiFi),第三方 App 不能绕过飞行模式的意图
(接上文
setWifiEnabled()后半段——同一个方法,处理闸门 4-6)
// ... 接上文 setWifiEnabled() 后半段// 闸门4:热点运行中(pre-S 设备)if (!SdkLevel.isAtLeastS() && !isPrivileged&& mTetheredSoftApTracker.getState().getState() == WIFI_AP_STATE_ENABLED) {mLog.err("setWifiEnabled with SoftAp enabled: only Settings can toggle wifi").flush();return false;}// 闸门5:用户限制(T+ 设备)long ident = Binder.clearCallingIdentity();try {if (SdkLevel.isAtLeastT() && mUserManager.hasUserRestrictionForUser(UserManager.DISALLOW_CHANGE_WIFI_STATE,UserHandle.getUserHandleForUid(callingUid))&& !isDeviceOrProfileOwner(callingUid, packageName)) {mLog.err("setWifiEnabled with user restriction: only DO/PO can toggle wifi").flush();return false;}} finally {Binder.restoreCallingIdentity(ident);}// 闸门6:旧版App确认对话框if (enable && isTargetSdkLessThanQ && isThirdParty&& showDialogWhenThirdPartyAppsEnableWifi()) {mLog.info("setWifiEnabled must show user confirmation dialog for uid=%").c(callingUid).flush();mWifiThreadRunner.post(() -> {// 异步投递到 WiFi 线程——投递与执行之间可能有时差if (mActiveModeWarden.getWifiState() == WIFI_STATE_ENABLED) {return; // WiFi 已被其他方式启用,跳过弹框}showWifiEnableRequestDialog(callingUid, callingPid, packageName);}, TAG + "#setWifiEnabled");return true;}setWifiEnabledInternal(packageName, enable, callingUid, callingPid, isPrivileged);return true;}
后三个闸门关注的是"当前系统场景下开 WiFi 会不会引起冲突":
•闸门 4 阻止了 Pre-S 设备上热点和 STA 同时存在的冲突——大部分旧芯片不支持 STA+AP 并发,如果热点开着,打开 STA 会导致其中一个接口被迫关闭。Android S+ 的设备通常支持并发,所以这个限制被移除了•闸门 5 是 Android T 引入的企业设备策略(Device Policy)支持——IT 管理员可以通过 DISALLOW_CHANGE_WIFI_STATE 限制工作设备上的 WiFi 操作。注意这里调用了 Binder.clearCallingIdentity():用户限制检查需要以 system 身份读取 UserManager 数据,不是所有调用者都有这个权限•闸门 6 是一个特殊的兼容路径——targetSdk < Q 的旧版 App 仍然可以请求打开 WiFi,但必须先弹出确认对话框让用户知情。注意它返回的是 true 而非 false:这里不是直接调用showWifiEnableRequestDialog(),而是通过 mWifiThreadRunner.post() 将弹框逻辑异步投递到 WiFi 线程——投递与执行之间可能有时差,lambda 内会先检查 getWifiState() == WIFI_STATE_ENABLED,如果 WiFi 已被其他方式启用则跳过弹框。用户点击"允许"后,对话框回调会再次调用 setWifiEnabled(),此时 showDialogWhenThirdPartyAppsEnableWifi() 返回 false(不再弹框),流程才会到达 setWifiEnabledInternal()
这 6 道闸门对应的调用者身份组合,用一张表总结会更清晰:
NETWORK_SETTINGS) | ||||
isSystem) | ||||
异常路径提示:以上任何一道闸门不通过,
setWifiEnabled()都直接返回false,App 端只能拿到一个布尔返回值,看不到具体的拒绝原因。这正是 Android 权限模型的设计意图——不向第三方 App 泄露系统状态(如是否处于飞行模式)。系统 App(Settings)可以通过NETWORK_SETTINGS权限绕过大部分闸门,唯独受限于UserManager的用户限制(如企业设备策略),此时 WifiService 也不会抛出异常,而是静默返回 false。下面逐闸门列出不通过时的返回值和日志——如果你在调试 WiFi 打不开的问题,这些日志关键字可以直接用于 logcat 过滤:
isThirdParty && !isTargetSdkLessThanQ | "setWifiEnabled not allowed for uid=%" | false | |
mSettingsStore.isSatelliteModeOn() | "setWifiEnabled not allowed as satellite mode is on." | false | |
isAirplaneModeOn && !isPrivileged | "setWifiEnabled in Airplane mode: only Settings can toggle wifi" | false | |
!SdkLevel.isAtLeastS() && !isPrivileged && AP enabled | "setWifiEnabled with SoftAp enabled: only Settings can toggle wifi" | false | |
DISALLOW_CHANGE_WIFI_STATE && !isDeviceOrProfileOwner | "setWifiEnabled with user restriction: only DO/PO can toggle wifi" | false | |
enable && isTargetSdkLessThanQ && isThirdParty | "setWifiEnabled must show user confirmation dialog for uid=%" | true |
2.3 setWifiEnabledInternal() — 通过检查后的真正入口
所有闸门都通过后,调用进入 setWifiEnabledInternal():
// service/java/com/android/server/wifi/WifiServiceImpl.javaprivate void setWifiEnabledInternal(String packageName, boolean enable,int callingUid, int callingPid, boolean isPrivileged) {long ident = Binder.clearCallingIdentity();try {if (!mSettingsStore.handleWifiToggled(enable)) {return; // 无法切换状态,直接返回}} finally {Binder.restoreCallingIdentity(ident);}// ... 省略:清理旧对话框(mWifiEnableRequestDialogHandles)、权限分级指标// (autoJoin 重启用/关闭日志)、InterfaceConflictManager 重置、// toggle 计数器递增、WiFi 状态变更广播 ...mActiveModeWarden.wifiToggled(new WorkSource(callingUid, packageName));mLastCallerInfoManager.put(WifiManager.API_WIFI_ENABLED, Process.myTid(),callingUid, callingPid, packageName, enable);}
主要功能:
•mSettingsStore.handleWifiToggled(enable) 将用户的开关状态持久化到 Settings 数据库——如果当前状态已经等于目标状态(如 WiFi 已经开着又点了"开"),方法返回 false,后续流程全部跳过,这是一个幂等优化•核心动作只有一行:mActiveModeWarden.wifiToggled(...)——把控制权交给 WiFi 子系统的"总调度员"•WorkSource 对象携带了本次操作的调用者信息(UID + 包名),用于后续的 blame 链追踪•Binder.clearCallingIdentity() 再次出现——持久化设置需要以 system 身份写 Settings,不能以 App 身份
到这里,权限检查和场景约束都完成了。"电流"已经通过了配电箱的断路器面板,接下来该总调度室决定启用哪条供电线路。
三、ActiveModeWarden 是怎么调度 WiFi 子系统的?
一句话总结:
ActiveModeWarden是 WiFi 子系统的"总调度员",它不亲自执行 WiFi 的开关逻辑,而是通过内部的WifiController状态机来管理 STA / SoftAp / ScanOnly 三种模式之间的切换。
3.1 为什么需要一个"总调度员"?
WiFi 子系统不是只有 STA(连 AP)这一种模式。同一个物理芯片上,可能同时需要支持:
•STA 模式(连接 AP,就是本文讲的场景)•SoftAp 模式(开启热点,让别人连你)•ScanOnly 模式(只扫描、不连接,Android 的位置服务需要这个)•STA+STA 双连接(同时连两个 AP,Android 12+ 支持)•STA+AP 并发(自己连着 WiFi 的同时开热点,部分芯片支持)
这些模式之间有冲突——比如你不能在同一个接口上既做 STA 又做 SoftAp。谁来仲裁这些冲突?谁来保证模式切换时不会留下"半死不活"的中间状态?
这就是 ActiveModeWarden 存在的意义。回到大楼供电比喻——它就像大楼的配电总控室,看着整栋楼的用电需求("3 楼要开灯"、"5 楼要用空调"、"地下室要充电"),然后决定启用哪几条供电干线,同时确保不会因为同时开太多线路而导致跳闸。
3.2 wifiToggled() — 一句话,把活交给状态机
从 WifiServiceImpl 过来的请求,在 ActiveModeWarden 里只做了一件事:
// service/java/com/android/server/wifi/ActiveModeWarden.javapublic void wifiToggled(WorkSource requestorWs) {mWifiController.sendMessage(WifiController.CMD_WIFI_TOGGLED, requestorWs);}
主要功能:
•函数体只有一行——发送 CMD_WIFI_TOGGLED 消息给内部状态机•WifiController 是 ActiveModeWarden 的内部类,不是独立文件(两者定义在同一个 .java 文件中)•这是典型的 StateMachine 消息驱动模式——外部只发送"发生了什么"(Event),内部状态机根据当前状态决定"该做什么"(Action)
但 ActiveModeWarden 不只是个消息转发器。它还持有 WiFi 子系统的核心资源引用:
// service/java/com/android/server/wifi/ActiveModeWarden.javapublic class ActiveModeWarden {// Holder for active mode managersprivate final Set<ConcreteClientModeManager> mClientModeManagers = new ArraySet<>();private final Set<SoftApManager> mSoftApManagers = new ArraySet<>();private final DefaultClientModeManager mDefaultClientModeManager;private final WifiInjector mWifiInjector;private final WifiNative mWifiNative;private final WifiController mWifiController; // 内部类状态机private final AtomicInteger mWifiState = new AtomicInteger(WIFI_STATE_DISABLED);// ...其他字段省略(mWifiMetrics、mSettingsStore、mScanRequestProxy 等约 30 个字段)...}
主要功能:
•两组 Set 分别管理 STA 模式管理器(ConcreteClientModeManager)和 AP 模式管理器(SoftApManager)•mWifiState 是一个 AtomicInteger,对外暴露 WiFi 开关状态(DISABLED / DISABLING / ENABLED / ENABLING / UNKNOWN)•mWifiNative 直接持有原生层的引用——它在 ActiveModeWarden 构造函数里注册了一个 daemon 崩溃监听,一旦 wpa_supplicant 挂了,自动触发 SelfRecovery
ActiveModeWarden 的构造函数(约 60 行)只做两件事:存储依赖注入(接收 14 个外部依赖参数)+ 创建内部的 WifiController 状态机。没有任何实际的 WiFi 操作——一切工作都推迟到状态机启动后才发生。
四、WifiController 状态机是怎么工作的?
一句话总结:
WifiController是一个只有两个子状态的状态机——DisabledState和EnabledState。它不关心 WiFi 连接的具体细节,只决定"WiFi 该不该开"这个最高层级的决策。
4.1 为什么需要状态机?
如果 WiFi 的开启/关闭只是简单的 if (on) { startWifi() } else { stopWifi() },那确实不需要状态机。但现实场景比这复杂得多:
•WiFi 正在关闭时,用户又点了开启——怎么办?•紧急呼叫模式下,运营商要求 WiFi 关闭——怎么覆盖用户的设置?•飞行模式切换时,WiFi 的状态要不要恢复?•SelfRecovery 触发重启时,WiFi 应该回到什么状态?
搞状态机的本质是:把"在什么状态下收到什么消息该做什么"这个决策逻辑显式化,而不是散落在几十个 if-else 里。Android 框架提供了一个基础的 StateMachine 类,WifiController 就是基于它构建的。
回到大楼供电比喻——WifiController 就像一个带有互锁装置的变压器总开关。它只有两个位置:"合闸"和"分闸",但它能保证无论什么时候有人去扳它(哪怕你刚把它拉到一半又推回去),结果都是确定的。
4.2 三层状态层级
WifiController 的状态层次很简洁:
StateMachine└── WifiController├── DefaultState (顶层——处理所有子状态未覆盖的消息)│ ├── DisabledState (WiFi 关闭)│ └── EnabledState (WiFi 打开)
状态转移全景(→ 标注触发消息):

构造代码:
// service/java/com/android/server/wifi/ActiveModeWarden.java// WifiController 内部类(约 L1930)WifiController() {super(TAG, mLooper);final int threshold = mResourceCache.getInteger(R.integer.config_wifiConfigurationWifiRunnerThresholdInMs);DefaultState defaultState = new DefaultState(threshold);mEnabledState = new EnabledState(threshold);mDisabledState = new DisabledState(threshold);addState(defaultState); {addState(mDisabledState, defaultState);addState(mEnabledState, defaultState);}}
主要功能:
•DefaultState 是父状态——DisabledState 和 EnabledState 都是它的子状态•子状态中未处理的消息会自动"冒泡"到父状态——这就是为什么 DefaultState 里集中处理了飞行模式、卫星模式等跨状态通用消息•只有两个子状态,因为 WiFi 的顶层决策确实只有两个答案:"开"或"关"
DefaultState 作为顶层,处理的消息包括:
•CMD_AIRPLANE_TOGGLED:飞行模式开 → 关掉一切(STA + AP);飞行模式关 → 根据 toggle 状态决定是否开 STA•CMD_SATELLITE_MODE_CHANGED:卫星模式开 → 关掉一切(卫星通信与 WiFi 共享天线资源,不能同时用)•CMD_RECOVERY_DISABLE_WIFI:SelfRecovery 限流后,放弃重启,直接关闭 WiFi
4.3 DisabledState → EnabledState:一次完整的转移
WiFi 从关闭到打开的核心状态转移发生在 DisabledState 中。当 CMD_WIFI_TOGGLED 消息到达时:
// ActiveModeWarden.java (WifiController 内部类, DisabledState)case CMD_WIFI_TOGGLED:case CMD_SCAN_ALWAYS_MODE_CHANGED:handleStaToggleChangeInDisabledState((WorkSource) msg.obj);break;
handleStaToggleChangeInDisabledState() 的判断逻辑:
// ActiveModeWarden.javaprivate void handleStaToggleChangeInDisabledState(WorkSource requestorWs) {if (shouldEnableSta()) {startPrimaryOrScanOnlyClientModeManager(requestorWs);transitionTo(mEnabledState);}}
主要功能:
•先调用 shouldEnableSta() 判断是否可以开 STA——检查 toggle 开关 AND 卫星模式•如果可以,调用 startPrimaryOrScanOnlyClientModeManager() 创建一个 ConcreteClientModeManager•然后 transitionTo(mEnabledState) 完成状态转移•注意顺序:先创建 Manager,再转移到 EnabledState——这是因为 EnabledState 的 enterImpl() 会断言至少有一个 Manager 存在
shouldEnableSta() 的实现:
// ActiveModeWarden.javaprivate boolean shouldEnableSta() {return (mSettingsStore.isWifiToggleEnabled() || shouldEnableScanOnlyMode())&& !mSettingsStore.isSatelliteModeOn();}
如果只是普通的"WiFi 开关打开"场景(不是 ScanOnly),startPrimaryOrScanOnlyClientModeManager() 会调用到 startPrimaryClientModeManager():
// ActiveModeWarden.javaprivate boolean startPrimaryClientModeManager(WorkSource requestorWs) {if (hasPrimaryOrScanOnlyModeManager()) {Log.e(TAG, "Unexpected state - primary CMM should not be started when a primary "+ "or scan only CMM is already present.");if (!mIsMultiplePrimaryBugreportTaken) {mIsMultiplePrimaryBugreportTaken = true;mWifiDiagnostics.takeBugReport("Wi-Fi ActiveModeWarden bugreport","Trying to start primary mode manager when one already exists.");}return false;}Log.d(TAG, "Starting primary ClientModeManager in connect mode");ConcreteClientModeManager manager = mWifiInjector.makeClientModeManager(new ClientListener(), requestorWs, ROLE_CLIENT_PRIMARY, mVerboseLoggingEnabled);mClientModeManagers.add(manager);mLastPrimaryClientModeManagerRequestorWs = requestorWs;return true;}
主要功能:
•防重复:如果已经有一个 primary/scanOnly Manager 存在,直接返回 false•通过 mWifiInjector.makeClientModeManager() 工厂方法创建 ConcreteClientModeManager——这是依赖注入的标准模式,便于测试时 mock•ClientListener 是一个回调对象,它会在 Manager 启动成功(onStarted())、失败(onStartFailure())、停止(onStopped())时被通知•Manager 加入 mClientModeManagers 集合后,其自身的 StateMachine 就会开始运转
WifiController 在启动时还有一个特殊路径——如果设备在上次关机时 WiFi 是开着的,start() 方法会直接初始化在 EnabledState:
// ActiveModeWarden.java:1974 (WifiController.start())public void start() {boolean isAirplaneModeOn = mSettingsStore.isAirplaneModeOn();boolean isWifiEnabled = mSettingsStore.isWifiToggleEnabled();boolean isScanningAlwaysAvailable = mSettingsStore.isScanAlwaysAvailable();boolean isLocationModeActive = mWifiPermissionsUtil.isLocationModeEnabled();boolean isSatelliteModeOn = mSettingsStore.isSatelliteModeOn();// 用 Settings 的初始值填充 Requestor 来源mLastPrimaryClientModeManagerRequestorWs = mFacade.getSettingsWorkSource(mContext);mLastScanOnlyClientModeManagerRequestorWs = INTERNAL_REQUESTOR_WS;ActiveModeManager.ClientRole role = getRoleForPrimaryOrScanOnlyClientModeManager();if (role == ROLE_CLIENT_PRIMARY) {startPrimaryClientModeManager(mLastPrimaryClientModeManagerRequestorWs);setInitialState(mEnabledState);} else if (role == ROLE_CLIENT_SCAN_ONLY) {startScanOnlyClientModeManager(mLastScanOnlyClientModeManagerRequestorWs);setInitialState(mEnabledState);} else {setInitialState(mDisabledState);}mWifiNative.initialize(); // 注册 Vendor HAL/wificond 死亡通知框架super.start(); // 启动 StateMachine 消息循环}
主要功能:
•从 5 个 Settings 开关读取初始状态(飞行模式、WiFi toggle、ScanAlways、位置模式、卫星模式)•getRoleForPrimaryOrScanOnlyClientModeManager() 综合这些开关决定初始角色——是 PRIMARY(可连接)还是 SCAN_ONLY(只扫描)还是都不启动•mWifiNative.initialize() 在状态机启动前注册底层基础设施(Vendor HAL 和 wificond 的死亡通知框架)——注意这个调用在条件分支之外,无论 WiFi 初始状态是开还是关都会执行。wpa_supplicant 不在此处启动(见第 9.2 节的 lazy HAL 机制)•super.start() 启动 Android StateMachine 的消息泵——之后所有 sendMessage() 才会被处理
WifiNative.initialize() 的职责:这个方法和第六章的 startHal() 是不同的入口,它们有明确的职责边界:
// service/java/com/android/server/wifi/WifiNative.java:1451public boolean initialize() {synchronized (mLock) {if (!mWifiVendorHal.initialize(new VendorHalDeathHandlerInternal())) {Log.e(TAG, "Failed to initialize vendor HAL");return false;}mWifiCondManager.setOnServiceDeadCallback(new WificondDeathHandlerInternal());mWifiCondManager.tearDownInterfaces();mWifiVendorHal.registerRadioModeChangeHandler(new VendorHalRadioModeChangeHandlerInternal());mNetdWrapper = mWifiInjector.makeNetdWrapper();return true;}}
主要功能:
•注册死亡通知:为 Vendor HAL 和 wificond 分别注册死亡回调(DeathHandlerInternal),一旦底层进程崩溃,Framework 能立即感知并触发 SelfRecovery。wpa_supplicant 的死亡回调在 SupplicantStaIfaceHal.initialize() 中单独注册•清理残留接口:tearDownInterfaces() 清除上次运行可能遗留的 wificond 接口,确保干净的初始状态•注册 Radio Mode 变更回调:监听底层芯片的模式切换(如 WiFi 和蓝牙共享天线时的模式变更)
initialize() vs startHal() 的职责边界:
•initialize():一次性准备工作——注册死亡通知、清理残留、建立回调框架。只在系统启动时执行一次•startHal():按需拉活——当有接口请求时才真正启动 HAL 进程。每次 WiFi 从关闭到打开都可能执行
4.4 EnabledState:WiFi 开着时,状态机在做什么?
上一节展示了如何『进入』EnabledState,但进入之后呢?EnabledState 不是空的——它在等待两类消息:
消息一:用户再次点击开关(CMD_WIFI_TOGGLED)
// ActiveModeWarden.java (WifiController 内部类, EnabledState)case CMD_WIFI_TOGGLED:// 用户点了"关闭 WiFi"transitionTo(mDisabledState);break;
WiFi 打开后,用户再次点击开关,EnabledState 直接转移到 DisabledState——这会触发 DisabledState.enterImpl() 中调用 stopPrimaryClientModeManager(),进而停止 ConcreteClientModeManager、关闭 HAL、卸载驱动。
消息二:飞行模式/卫星模式/Recovery 等全局事件
这些消息在 DefaultState 中处理(第 4.2 节已列出),无论当前是 DisabledState 还是 EnabledState,都会触发。飞行模式开启时,DefaultState 处理 CMD_AIRPLANE_TOGGLED——关掉 STA 和 AP,并从 EnabledState 转移到 DisabledState。
消息三:STA 接口就绪后的模式切换
// ActiveModeWarden.java (WifiController 内部类, EnabledState)case CMD_CLIENT_MODE_MANAGER_STATUS_CHANGED:// ConcreteClientModeManager 报告自己已经启动或停止break;
当 ConcreteClientModeManager 成功初始化 STA 接口后,它向 WifiController 发送状态变更通知。EnabledState 据此更新 mWifiState(ENABLING → ENABLED),Framework 上层通过 WifiManager.getWifiState() 就能看到 WiFi 已完全就绪。
状态转移一图总结:
CMD_WIFI_TOGGLEDDisabledState ──────────────────────────→ EnabledState↑ ││ CMD_WIFI_TOGGLED │└────────────────────────────────────────┘↑ ││ CMD_AIRPLANE_TOGGLED / SATELLITE │└────────────────────────────────────────┘(DefaultState 统一处理)
为什么 EnabledState 这么简单? 因为
WifiController只管"WiFi 该不该开"这个最高决策。一旦决定"该开",具体的执行(创建设备接口、启动 HAL、初始化 Supplicant)全部委托给ConcreteClientModeManager。EnabledState的核心工作是两件事:① 监听关闭指令(用户关开关、飞行模式),② 监听子 Manager 的状态报告(用于更新 WiFi 状态指示)。
至此,WiFi 的顶层决策已经完成。WifiController 由 DisabledState 转移到了 EnabledState,ConcreteClientModeManager 也已经创建。接下来该看这个"楼层配电柜"是怎么启动的了。
五、ConcreteClientModeManager 和 ClientModeImpl 是怎么初始化的?
一句话总结:
ConcreteClientModeManager是 STA 模式的"大管家",它内部自带一个ClientModeStateMachine子状态机来管理接口生命周期,在ConnectModeState下会创建更底层的ClientModeImpl(一个拥有 9 个状态的连接管理状态机)。
回到大楼供电比喻——前面几章已经把变压器总闸合上了(WifiController 转移到 EnabledState),现在该逐层合上楼层配电柜的闸。ConcreteClientModeManager 就是 3 楼的配电柜:它的工作是把总闸送来的电("已启用"状态)分配到具体的电器线路(接口模式:是连接还是只扫描),并且安装一个"漏电保护器"(ClientModeImpl 的 9 状态 FSM 负责监控连接全生命周期的异常)。
5.1 ConcreteClientModeManager 的创建与启动
ConcreteClientModeManager 有三个角色(对应 ClientRole 枚举):
ROLE_CLIENT_PRIMARY | ||
ROLE_CLIENT_SECONDARY | ||
ROLE_CLIENT_SCAN_ONLY |
本文关注的是 ROLE_CLIENT_PRIMARY(普通 WiFi 连接)的初始化路径。
ConcreteClientModeManager 的构造函数:
// service/java/com/android/server/wifi/ConcreteClientModeManager.javaConcreteClientModeManager(WifiContext context, ..., ClientRole role,WifiNative wifiNative, ...) {// 存储依赖注入的各种对象mStateMachine = new ClientModeStateMachine(looper);// ... 发送 CMD_START 给内部状态机sendMessage(CMD_START, new RoleChangeInfo(role, requestorWs, listener));}
构造完成后,内部的 ClientModeStateMachine 开始运转。它有自己的 4 个状态:
IdleState (初始状态)└── StartedState (接口已就绪)├── ScanOnlyModeState (仅扫描,不创建 ClientModeImpl)└── ConnectModeState (连接模式,创建 ClientModeImpl)
状态转移:

对于 ROLE_CLIENT_PRIMARY,CMD_START 处理后进入 IdleState,然后立即切换到 StartedState,再根据角色切换到 ConnectModeState。
5.2 ClientModeImpl 的创建——ConnectModeState 的 enterImpl
当 ConnectModeState 被进入时,关键的初始化发生:
// service/java/com/android/server/wifi/ConcreteClientModeManager.java:1222// ConnectModeState.enterImpl()mClientModeImpl = mWifiInjector.makeClientModeImpl(mClientInterfaceName, ConcreteClientModeManager.this, mVerboseLoggingEnabled);
这一行触发了 WifiInjector.makeClientModeImpl(),它内部创建了一个 new ClientModeImpl(...),传入约 58 个依赖参数(Context、WifiMetrics、Clock、WifiNative、WifiConfigManager、PasspointManager、WifiMonitor 等等)。ClientModeImpl 构造完成后,会立即构建自己的 9 状态层级并调用 start() 开始运转。
5.3 ClientModeImpl 的 9 个状态(速览)
ClientModeImpl 是 WiFi 连接的"心脏",它管理从断开到完全连接的全生命周期。本文只做速览,详细的状态转移留给连接章节。
ConnectableState(根状态)—— 过滤无效连接请求,分发到子状态│├── DisconnectedState —— 空闲,无连接│ (父: ConnectableState,与 ConnectingOrConnectedState 平级)│└── ConnectingOrConnectedState —— 所有"非空闲"状态的父容器(父: ConnectableState,与 DisconnectedState 平级)│├── L2ConnectingState —— 802.11 关联/认证进行中│ (父: ConnectingOrConnectedState,与 L2ConnectedState 平级)│└── L2ConnectedState —— L2 链路已建立,承载 L3 子状态(父: ConnectingOrConnectedState,与 L2ConnectingState 平级)│├── WaitBeforeL3ProvisioningState —— 等待 DHCP 前操作│ (父: L2ConnectedState,四个子状态互斥)├── L3ProvisioningState —— 获取 IP 地址│ (父: L2ConnectedState)├── L3ConnectedState —— 完全连接,IP 就绪│ (父: L2ConnectedState)└── RoamingState —— 正在漫游到另一个 AP(父: L2ConnectedState)
9 个状态的职责简述:
ConnectableState | |||
DisconnectedState | ConnectableState | CMD_START_CONNECT | |
ConnectingOrConnectedState | ConnectableState | ||
L2ConnectingState | ConnectingOrConnectedState | ||
L2ConnectedState | ConnectingOrConnectedState | ||
WaitBeforeL3ProvisioningState | L2ConnectedState | ||
L3ProvisioningState | L2ConnectedState | ||
L3ConnectedState | L2ConnectedState | ||
RoamingState | L2ConnectedState |
这些状态我们会在后续的连接章节中展开分析。本文到达 HAL 就绪即止——此时 ClientModeImpl 还停留在 DisconnectedState,等待后续的连接指令。
六、HalDeviceManager 是怎么启动 HAL 进程的?
一句话总结:
HalDeviceManager是 Java 框架与 Native HAL 之间的"门卫"——它通过 AIDL/HIDL Binder 调用拉起vendor.wifi_hal服务进程,并管理重试逻辑。
回到大楼供电比喻——前面几章已经把楼层配电柜的闸逐一合上(ConcreteClientModeManager → ClientModeImpl),现在要接通大楼与电网之间的变压器(HAL 层)。变压器的"合闸"不是打开开关就完事——需要先确认型号(HIDL vs AIDL)、等待预热(retry 逻辑)、然后才能送出电流。
6.1 先澄清一个易混淆点:HAL 何时被启动?
在深入源码之前,必须回答一个关键问题:HAL 是系统启动时就启动的,还是用户点击开关时才启动的?
•系统启动时:WifiController.start() 调用 mWifiNative.initialize(),注册 Vendor HAL 和 wificond 的死亡通知框架、清理残留接口。此时 HAL 进程本身尚未启动——它只搭建了"当 HAL 崩溃时通知我"的回调基础设施(详见第四章 4.3 节末尾的 initialize() vs startHal() 职责边界)。•用户点击开关时:走到本章的路径——ConnectModeState.enterImpl() → WifiNative.startHal() → HalDeviceManager.startWifi(),显式拉起vendor.wifi_hal 进程。
本章聚焦第二种场景。下面的调用链展示了从 Framework 到 HAL 进程拉起的完整路径:
ConnectModeState.enterImpl()→ WifiNative.setupInterfaceForClientInScanMode()→ WifiNative.startHal() ← HAL 在这里显式启动→ WifiVendorHal.startVendorHal()→ HalDeviceManager.start()→ HalDeviceManager.startWifi()→ createStaIface()→ mWifiCondManager.setupInterfaceForClientMode()→ mWificond.createClientInterface() ← wificond 接口在这里创建
6.2 Framework 到 HAL 的真正桥接点:setupInterfaceForClientMode()
在 ConnectModeState.enterImpl() 创建了 ClientModeImpl 之后,Framework 需要向 wificond 守护进程注册一个 STA 接口。这个桥接点就是 WifiNl80211Manager.setupInterfaceForClientMode()——它通过 Binder 调用 wificond,创建 IClientInterface 并注册扫描回调:
// base/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.javapublic boolean setupInterfaceForClientMode(@NonNull String ifaceName,@NonNull @CallbackExecutor Executor executor,@NonNull ScanEventCallback scanCallback,@NonNull ScanEventCallback pnoScanCallback) {Log.d(TAG, "Setting up interface for client mode: " + ifaceName);if (!retrieveWificondAndRegisterForDeath()) {return false; // wificond 服务未就绪}if (scanCallback == null || pnoScanCallback == null || executor == null) {Log.e(TAG, "setupInterfaceForClientMode invoked with null callbacks");return false;}IClientInterface clientInterface = null;try {clientInterface = mWificond.createClientInterface(ifaceName);} catch (RemoteException e1) {Log.e(TAG, "Failed to get IClientInterface due to remote exception");return false;} catch (NullPointerException e2) {Log.e(TAG, "setupInterfaceForClientMode NullPointerException");return false;}if (clientInterface == null) {Log.e(TAG, "Could not get IClientInterface instance from wificond");return false;}Binder.allowBlocking(clientInterface.asBinder());// ... 注册扫描回调见下段
这一段做的是接口创建:三步验证(wificond 就绪 + 参数非空 + 创建成功),外加一个 Binder.allowBlocking() 允许后续操作阻塞当前线程。但接口创建只是拿到了一个句柄——框架还没有办法从 wificond 收到任何扫描结果。要让这个接口"活起来",必须注册扫描事件回调:
// ... 接上文,IClientInterface 已创建,继续注册扫描回调mClientInterfaces.put(ifaceName, clientInterface);try {IWifiScannerImpl wificondScanner = clientInterface.getWifiScannerImpl();if (wificondScanner == null) {Log.e(TAG, "Failed to get WificondScannerImpl");return false;}mWificondScanners.put(ifaceName, wificondScanner);Binder.allowBlocking(wificondScanner.asBinder());ScanEventHandler scanEventHandler = new ScanEventHandler(executor, scanCallback);mScanEventHandlers.put(ifaceName, scanEventHandler);wificondScanner.subscribeScanEvents(scanEventHandler);PnoScanEventHandler pnoScanEventHandler = new PnoScanEventHandler(executor,pnoScanCallback);mPnoScanEventHandlers.put(ifaceName, pnoScanEventHandler);wificondScanner.subscribePnoScanEvents(pnoScanEventHandler);} catch (RemoteException e) {Log.e(TAG, "Failed to refresh wificond scanner due to remote exception");}return true;}
主要功能:
•retrieveWificondAndRegisterForDeath() 获取 wificond 的 Binder 代理并注册死亡通知——这是 HAL 层与 Framework 之间的"心跳线"•mWificond.createClientInterface(ifaceName) 通过 Binder 调用 wificond,创建 STA 模式的 IClientInterface——这一行是真正的跨进程桥接点•创建接口后立即注册两个扫描回调:ScanEventHandler(普通扫描)和 PnoScanEventHandler(离线扫描/Preferred Network Offload)——设计上把它们放在同一个 try-catch 里,说明这是一个原子操作:要么两个回调都注册成功,要么都失败•Binder.allowBlocking() 允许 Binder 调用阻塞当前线程——因为某些 wificond 操作(尤其是 scanner 操作)可能耗时较长,不允许阻塞会导致 RemoteException。
setupInterfaceForClientMode() 是在WifiNl80211Manager中定义的,但 Framework 上层通过WifiNative来间接调用它(WifiNative持有mWifiCondManager,即WifiNl80211Manager的实例)。需要注意的是,HAL 启动不是由createClientInterface()间接触发的——在setupInterfaceForClientInScanMode()中,startHal()在createClientInterface() 之前就被显式调用了(详见下方调用链)。
WifiNative.startHal() 的实现:
// service/java/com/android/server/wifi/WifiNative.javaprivate boolean startHal() {synchronized (mLock) {if (!mIfaceMgr.hasAnyIface()) {if (mWifiVendorHal.isVendorHalSupported()) {if (!mWifiVendorHal.startVendorHal()) {Log.e(TAG, "Failed to start vendor HAL");return false;}if (SdkLevel.isAtLeastS()) {mWifiVendorHal.setCoexUnsafeChannels(mCachedCoexUnsafeChannels, mCachedCoexRestrictions);}} else {Log.i(TAG, "Vendor Hal not supported, ignoring start.");}}registerWificondListenerIfNecessary();return true;}}
WifiVendorHal.startVendorHal() 直接把锅甩给 HalDeviceManager:
// service/java/com/android/server/wifi/WifiVendorHal.javapublic boolean startVendorHal() {synchronized (sLock) {if (!mHalDeviceManager.start()) {mLog.err("Failed to start vendor HAL").flush();return false;}mLog.info("Vendor Hal started successfully").flush();return true;}}
6.3 HalDeviceManager.startWifi() — 带重试的 start
// service/java/com/android/server/wifi/HalDeviceManager.javapublic static final int START_HAL_RETRY_TIMES = 3; // 最多重试 3 次,即共 4 次尝试// ...省略其他代码...private boolean startWifi() {if (VDBG) Log.d(TAG, "startWifi");initializeInternal(); // 确保 HAL 已初始化(注册回调 + 死亡通知)synchronized (mLock) {int triedCount = 0;while (triedCount <= START_HAL_RETRY_TIMES) {int status = mWifiHal.start();if (status == WifiHal.WIFI_STATUS_SUCCESS) {managerStatusListenerDispatch();if (triedCount != 0) {Log.d(TAG, "start IWifi succeeded after trying "+ triedCount + " times");}WifiChipInfo[] wifiChipInfos = getAllChipInfo(false);if (wifiChipInfos == null) {Log.e(TAG, "Started wifi but could not get current chip info.");}return true;// ... 失败路径见下段
成功路径的核心逻辑是:拿到 WIFI_STATUS_SUCCESS → 通知监听者 → 查询芯片信息 → 返回。但现实并非总是这么顺利——下面的代码处理两种失败场景:
// ... 接上文 startWifi(),处理 HAL start 失败} else if (status == WifiHal.WIFI_STATUS_ERROR_NOT_AVAILABLE) {// HAL 进程正在停止中(上一次 stop 还没完成),重试Log.e(TAG, "Cannot start wifi because unavailable. Retrying...");try {Thread.sleep(START_HAL_RETRY_INTERVAL_MS);} catch (InterruptedException ignore) {// no-op}triedCount++;} else {// 其他错误,不重试Log.e(TAG, "Cannot start IWifi. Status: " + status);return false;}}Log.e(TAG, "Cannot start IWifi after trying " + triedCount + " times");return false;}}
主要功能:
•START_HAL_RETRY_TIMES = 3,即最多重试 3 次,加上初始尝试共 4 次机会•initializeInternal() 在每次 start 前都会调用——它内部只做一件事:mWifiHal.initialize(mIWifiDeathRecipient),注册 HAL 死亡回调。它是幂等的,已经初始化过就直接返回•重试逻辑只针对 ERROR_NOT_AVAILABLE 这一个错误码——它表示 HAL 进程还在 STOPPING 状态(上一次 stop 的清理工作还没完成)。这是有意设计的窄白名单:其他错误码(如 ERROR_UNKNOWN)表示 HAL 内部出了不可恢复的问题,重试毫无意义•成功后调用 getAllChipInfo(false) 查询芯片信息。注意即使 getAllChipInfo 返回 null(拿不到芯片信息),startWifi() 依然返回 true——芯片信息查询失败不会回滚 HAL 的启动,因为这是一个"尽力而为"的查询,不影响 HAL 的基本功能
这个重试逻辑的设计很有意思——它说明了一个事实:HAL 进程的 stop 不是瞬时的。如果用户快速开关 WiFi,上一个 stop 的清理可能还没结束,下一个 start 就来了。这时候必须等它清理完再启动。
异常路径提示:如果 retry 次数耗尽(START_HAL_RETRY_TIMES = 3,共 4 次尝试)仍然返回 ERROR_NOT_AVAILABLE,或 HAL 返回其他错误码,startWifi() 返回 false。这个 false 会沿调用链向上传播:HalDeviceManager.start() → WifiVendorHal.startVendorHal() → WifiNative.startHal()。WifiNative.startHal() 失败时只记录 "Failed to start vendor HAL" 日志,然后返回 false,不会重试也不会触发 SelfRecovery。这意味着如果 HAL 启动失败,WiFi 子系统会静默保持在 DisabledState,用户在 Settings 里看到的仍然是"WiFi 已关闭"—点击开关后图标转几圈就回到关闭状态。
在 logcat 中可以搜索以下关键字定位问题:
•"Cannot start IWifi after trying" — 4 次尝试全部失败•"Cannot start IWifi. Status:" — 非重试类错误(HAL 返回 WIFI_STATUS_ERROR_UNKNOWN)•"Cannot start wifi because unavailable. Retrying..." — 单次重试(HAL 还在 STOPPING 状态)•"Failed to start vendor HAL" — 最终失败,在 WifiVendorHal.java 中打印
6.4 HIDL vs AIDL:两条 HAL 通信路径
Android 的 HAL 通信经历了从 HIDL 到 AIDL 的迁移。WifiHal 类封装了这两种路径的差异:
// service/java/com/android/server/wifi/hal/WifiHal.javaprotected IWifiHal createWifiHalMockable(...) {if (WifiHalHidlImpl.serviceDeclared()) {return new WifiHalHidlImpl(context, ssidTranslator); // HIDL 路径} else if (WifiHalAidlImpl.serviceDeclared()) {return new WifiHalAidlImpl(context, ssidTranslator); // AIDL 路径} else {Log.e(TAG, "No HIDL or AIDL service available for the Wifi Vendor HAL.");return null;}}
主要功能:
•优先尝试 HIDL,然后是 AIDL——这是为了向后兼容旧设备•两条路径实现同一个 IWifiHal 接口,对上层完全透明•如果两条路径都没有可用的服务,返回 null——这说明厂商没有提供 WiFi HAL 实现
AIDL 路径下,获取 HAL 服务的方式:
// service/java/com/android/server/wifi/hal/WifiHalAidlImpl.javaprotected android.hardware.wifi.IWifi getWifiServiceMockable() {if (SdkLevel.isAtLeastU()) {return android.hardware.wifi.IWifi.Stub.asInterface(ServiceManager.waitForDeclaredService(HAL_INSTANCE_NAME));}return null;}
其中 HAL_INSTANCE_NAME 是 "android.hardware.wifi.IWifi/default"。ServiceManager.waitForDeclaredService() 会阻塞等待直到服务注册完成。而 start() 方法会捕获两种 Binder 异常:
// service/java/com/android/server/wifi/hal/WifiHalAidlImpl.javapublic @WifiHal.WifiStatusCode int start() {final String methodStr = "start";synchronized (mLock) {try {if (!checkWifiAndLogFailure(methodStr)) return WifiHal.WIFI_STATUS_ERROR_UNKNOWN;mWifi.start(); // Binder 调用 → 进入 Native HAL Servicereturn WifiHal.WIFI_STATUS_SUCCESS;} catch (RemoteException e) {handleRemoteException(e, methodStr);return WifiHal.WIFI_STATUS_ERROR_REMOTE_EXCEPTION;} catch (ServiceSpecificException e) {handleServiceSpecificException(e, methodStr);return halToFrameworkWifiStatusCode(e.errorCode);}}}
mWifi.start() 是一个 Binder 调用,它跨进程进入 vendor.wifi_hal 服务进程,执行 wifi.cpp 中的 Wifi::start() 方法。
七、厂商 HAL 是怎么被加载的?
一句话总结:厂商 HAL 以共享库(.so)的形式存在,由
WifiLegacyHalFactory通过dlopen+dlsym加载,不同厂商(QCOM / MTK)在同一个init_wifi_vendor_hal_func_table()函数名下实现了完全不同的功能集合。
7.1 Native HAL 的 startInternal()
当 Binder 调用到达 vendor.wifi_hal 进程后,Wifi::start() 触发了 startInternal():
// hardware/interfaces/wifi/aidl/default/wifi.cppndk::ScopedAStatus Wifi::startInternal() {if (run_state_ == RunState::STARTED) {return ndk::ScopedAStatus::ok(); // 幂等:已启动}if (run_state_ == RunState::STOPPING) {return createWifiStatus(WifiStatusCode::ERROR_NOT_AVAILABLE); // 还在关,重试}// ... initializeModeControllerAndLegacyHal() 返回 ndk::ScopedAStatusif (auto wifi_status = initializeModeControllerAndLegacyHal(); !wifi_status.isOk()) {return wifi_status;}// 创建 WifiChip 对象...run_state_ = RunState::STARTED;// 通知 Java 侧:遍历所有注册的回调,逐一调用 onStart()for (const auto& callback : event_cb_handler_.getCallbacks()) {if (!callback->onStart().isOk()) {LOG(ERROR) << "Failed to invoke onStart callback";}}return ndk::ScopedAStatus::ok();}
主要功能:
•STARTED 状态直接返回成功——支持幂等调用•STOPPING 状态返回 ERROR_NOT_AVAILABLE——这就是 Java 侧重试逻辑触发的原因•initializeModeControllerAndLegacyHal() 是真正的初始化入口——它返回 ndk::ScopedAStatus(而非 bool),内部调用 WifiLegacyHalFactory 来加载厂商 .so•event_cb_handler_.getCallbacks() 返回所有已注册的 IWifiEventCallback 列表,代码遍历列表逐一调用 onStart()——这个回调会逐级向上传播,最终触发 ClientListener.onStarted()
initializeModeControllerAndLegacyHal() 的实现:
// hardware/interfaces/wifi/aidl/default/wifi.cpp// 精简说明:省略了 mode_controller_->initialize() 的返回值检查和日志ndk::ScopedAStatus Wifi::initializeModeControllerAndLegacyHal() {mode_controller_->initialize(); // 固件模式控制器初始化// 获取所有 Legacy HAL 对象(每个物理芯片一个)const auto legacy_hals = legacy_hal_factory_->getHals();if (legacy_hals.empty()) {return createWifiStatus(WifiStatusCode::ERROR_UNKNOWN);}for (const auto& legacy_hal : legacy_hals) {legacy_hal::wifi_error legacy_status = legacy_hal->initialize();if (legacy_status != legacy_hal::WIFI_SUCCESS) {return createWifiStatus(WifiStatusCode::ERROR_UNKNOWN);}}return ndk::ScopedAStatus::ok();}
legacy_hal_factory_.getHals() 返回的是 WifiLegacyHal 对象列表——每一个对象对应一个物理 WiFi 芯片的"遗留 HAL"封装。
7.2 两种加载方式:先"自家人",再"找外援"
WifiLegacyHalFactory 加载厂商 .so 分两步走:
第一步:静态链接(dlsym RTLD_DEFAULT)
// hardware/interfaces/wifi/aidl/default/wifi_legacy_hal_factory.cpp:774// 精简说明:省略了 initHalFuncTableWithStubs() 和 initfn() 的返回值检查boolWifiLegacyHalFactory::initLinkedHalFunctionTable(wifi_hal_fn* hal_fn){init_wifi_vendor_hal_func_table_t initfn;initfn = (init_wifi_vendor_hal_func_table_t)dlsym(RTLD_DEFAULT, "init_wifi_vendor_hal_func_table");if (!initfn) {LOG(INFO) << "no vendor HAL library linked, will try dynamic load";return false;}initHalFuncTableWithStubs(hal_fn); // 先用 stub 填满initfn(hal_fn); // 再让厂商覆盖return true;}
主要功能:
•dlsym(RTLD_DEFAULT, ...) 在当前进程的全局符号表中查找——如果厂商 .so 已经通过 LD_PRELOAD 或编译期链接进进程,这里就能找到•先调用 initHalFuncTableWithStubs() 把整个函数表填满"空实现"——防止函数指针为 NULL•再调用厂商的 initfn() 覆盖掉它实现了的那些函数•如果 dlsym 返回 NULL(没有厂商 .so 静态链接进来),返回 false 进入动态加载
第二步:动态加载(dlopen + XML 描述文件)
// hardware/interfaces/wifi/aidl/default/wifi_legacy_hal_factory.cpp:798// 精简说明:省略了 wifi_early_initialize() 的错误处理(实际允许 WIFI_ERROR_NOT_SUPPORTED)boolWifiLegacyHalFactory::loadVendorHalLib(const std::string& path, wifi_hal_lib_desc& desc) {void* h = dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL);if (!h) {LOG(ERROR) << "failed to open vendor hal library: " << path;return false;}init_wifi_vendor_hal_func_table_t initfn;initfn = (init_wifi_vendor_hal_func_table_t)dlsym(h, "init_wifi_vendor_hal_func_table");if (!initfn) {LOG(ERROR) << "init_wifi_vendor_hal_func_table not found in: " << path;dlclose(h);return false;}initHalFuncTableWithStubs(&desc.fn);initfn(&desc.fn);desc.fn.wifi_early_initialize(); // 厂商的早期初始化 hookdesc.handle = h;return true;}
主要功能:
•.so 的路径来自 /vendor/etc/wifi/vendor_hals/*.xml——每个芯片的 HAL 描述文件指定了 .so 的位置•RTLD_NOW | RTLD_LOCAL 表示立即解析所有符号,且符号不导出到全局(隔离不同厂商的 HAL)•加载成功后调用 wifi_early_initialize()——这是一个厂商早期的初始化 hook,可以在函数表注册之后、正式工作之前做一些准备工作•.so 的句柄保存下来,后续 dlclose 时用于卸载
异常路径提示:如果 dlopen()失败(.so 文件不存在、权限不足、或缺少依赖库),loadVendorHalLib()返回false,整个 LEGACY HAL 对象被标记为INVALID。Wifi::startInternal()会检测到没有任何可用的 Legacy HAL,返回ERROR_UNKNOWN。Java 侧的HalDeviceManager.startWifi()不再重试(ERROR_UNKNOWN不在重试白名单中),直接返回false。但有一个例外:如果厂商实现了 AIDL HAL 的完整接口(而不依赖 Legacy HAL 桥接),Wifi::startInternal()可能不需要 Legacy HAL 也能成功——这种情况下legacy_hal_factory_.getHals()返回空列表,initializeModeControllerAndLegacyHal()仍然返回true。
在 logcat 中搜索以下关键字定位 dlopen 失败:
•"failed to open vendor hal library: " + .so 路径 — dlopen 本身失败(文件不存在、权限、依赖缺失)•"init_wifi_vendor_hal_func_table not found in: " + .so 路径 — .so 加载成功但没导出 init_wifi_vendor_hal_func_table 符号(厂商 .so 不符合 AOSP 接口约定)•"no vendor HAL library linked, will try dynamic load" — 静态链接路径未找到,进入 dlopen 动态加载路径(这是正常流程,不是错误)
7.3 QCOM vs MTK:同一个接口,两个世界
两家厂商都导出同一个函数签名:
wifi_error init_wifi_vendor_hal_func_table(wifi_hal_fn *fn);这个函数由 AIDL HAL 的 WifiLegacyHalFactory 通过 dlsym 加载(见 7.2 节)。但两家厂商的实现策略差异巨大。
QCOM(高通)的实现:
QCOM 的 wifi_hal.cpp 中同样有完整的 init_wifi_vendor_hal_func_table(约 1072 行起),集中填充了 ~100 个函数指针,覆盖 AOSP 定义的几乎所有 HAL 函数。与 MTK 的区别在于:QCOM 采用"全功能"策略——即使某些功能在特定芯片上不支持,也提供返回"不支持"的实现,而非用 dummy 填充。其 wifi_initialize() 中除了标准的 nl80211 通道外,还通过 cld80211 库建立了一个额外的诊断通道(见 8.3 节),用于固件日志、逐包统计和 OEM 消息的上报。QCOM 的 HAL 包含大量私有扩展:SAR(人体吸收率功率管理)、温控、DTIM 配置、TWT(Target Wake Time)、VoIP 模式等。
MTK(联发科)的实现:
// MTK/hardware-mediatek-wlan/wifi_hal/wifi_hal.cppwifi_error init_wifi_vendor_hal_func_table(wifi_hal_fn* fn) {if (fn == NULL) returnWIFI_ERROR_UNKNOWN;fn->wifi_initialize = wifi_initialize;fn->wifi_cleanup = wifi_cleanup;fn->wifi_event_loop = wifi_event_loop;// ... 约 55 个真实函数实现(核心功能 + NAN + RTT + 链路统计)...// 另有约 5 个函数指向 _dummy 后缀的空实现(如 get_ring_buffers_status_dummy)// 用模板"虚函数"替代不需要的功能:populateDummyFor(fn->wifi_wait_for_driver_ready);populateDummyFor(fn->wifi_set_nodfs_flag);populateDummyFor(fn->wifi_set_log_handler);populateDummyFor(fn->wifi_reset_log_handler);populateDummyFor(fn->wifi_configure_nd_offload);populateDummyFor(fn->wifi_start_pkt_fate_monitoring);populateDummyFor(fn->wifi_get_ring_data);populateDummyFor(fn->wifi_start_logging);return WIFI_SUCCESS;}
主要功能:
•函数表共约 68 个函数指针(约 60 个真实实现 + 8 个 populateDummyFor 填充的空函数)•不需要的功能用 populateDummyFor<T>() 模板函数填充——返回值类型返回 WIFI_SUCCESS,void 类型啥也不做•这是典型的"最小实现"策略——降低维护成本,减少出 bug 的面•但这 8 个 dummy 函数在上层框架看来是"调用成功"但不是"功能正常"——如果 Framework 依赖这些函数的返回结果做决策,可能产生非预期行为
populateDummyFor 的实现利用了 C++ 模板特化:
// MTK/hardware-mediatek-wlan/wifi_hal/wifi_hal.cpptemplate <typename T>voidpopulateDummyFor(T& val){val = &DummyFunction<T>::invoke;}
这个模板为每种函数签名自动生成一个返回"成功"的空函数——比手写 8 个 stub 优雅得多。
QCOM vs MTK 对比总结:
populateDummyForWIFI_SUCCESS | ||
wifi_hal.cpp | wifi_hal.cpp |
回到大楼供电比喻——QCOM 的 HAL 像标准化的工业接口(USB-C:一口多用,但实现成本高),MTK 的 HAL 像定制化的专用接口(DC 圆头:功能单一,但够用且省事)。
进一步说,这就像两栋大楼装了不同品牌的变压器。QCOM 的变压器功能齐全,附带智能监控、远程诊断(CLD80211 的 6 个组播组)、自动调压;MTK 的变压器功能精简,只做最基本的电压转换,其他功能面板上装了"假开关"(dummy),按下去灯亮但什么都不会发生。Framework 作为"大楼管理员",并不关心变压器的品牌——它只需要变压器把电送进来。
八、nl80211 + ioctl:用户空间到内核的两条通信通道
一句话总结:HAL 层到 kernel 的通信有两条通道——nl80211(Generic Netlink) 负责标准 WiFi 操作(扫描、连接、密钥),ioctl(
SIOCDEVPRIVATE+1、SIOCGIFFLAGS、WEXTSIOCIWFIRSTPRIV+N)负责接口管理和厂商私有命令。每个厂商的wifi_initialize()都会独立创建 netlink socket 并解析 nl80211 family ID,同时也通过 ioctl 操作网络接口。
回到大楼供电比喻——到这里,大楼内部的配电线路已经全部就绪(WifiController 总闸合上 → ConcreteClientModeManager 楼层配电柜就绪 → HalDeviceManager 变压器通电),但还缺最后一根电缆:从变压器到电网的接线。nl80211 就是这根电缆——它把用户空间(HAL)和内核空间(cfg80211)连接起来,让上层的每一条命令都能送达 WiFi 芯片的驱动程序。
8.1 nl80211 是什么?
nl80211 是 Linux 内核中 cfg80211 子系统对外暴露的用户空间接口。它不是 TCP/UDP 这种传统 socket——它是 AF_NETLINK 协议族下的 NETLINK_GENERIC 子类型。简单理解:nl80211 是内核 WiFi 栈给用户空间开的一扇"专用窗口",所有标准 WiFi 操作(扫描、关联、断开、设置信道……)都通过这扇窗口发送命令。
8.2 Netlink socket 创建的标准流程
无论是 QCOM 还是 MTK 的 HAL,创建 nl80211 socket 的流程都是相同三步:
// 以 MTK 为例// MTK/hardware-mediatek-wlan/wifi_hal/wifi_hal.cppstatic nl_sock* wifi_create_nl_socket(int port){struct nl_sock* sock = nl_socket_alloc(); // 步骤 1:分配 socketif (!sock) return NULL;wifi_socket_set_local_port(sock, port); // 步骤 1.5:设置本地端口号if (nl_connect(sock, NETLINK_GENERIC)) { // 步骤 2:连接到 NETLINK_GENERICnl_socket_free(sock);return NULL;}return sock;}
然后在 wifi_initialize() 中:
// 创建两个 socket:一个发命令,一个收事件info->cmd_sock = wifi_create_nl_socket(WIFI_HAL_CMD_SOCK_PORT); // 命令通道info->event_sock = wifi_create_nl_socket(WIFI_HAL_EVENT_SOCK_PORT); // 事件通道// 解析 nl80211 family IDinfo->nl80211_family_id = genl_ctrl_resolve(cmd_sock, "nl80211");if (info->nl80211_family_id < 0) {// 致命错误——内核不支持 nl80211,级联释放所有已分配资源nl_socket_free(cmd_sock);nl_socket_free(event_sock);free(info->event_cb);free(info->cmd);free(info);return WIFI_ERROR_UNKNOWN;}// 订阅组播事件wifi_add_membership((wifi_handle)info, "scan");wifi_add_membership((wifi_handle)info, "mlme");wifi_add_membership((wifi_handle)info, "regulatory");wifi_add_membership((wifi_handle)info, "vendor");
主要功能:
•两个 socket 分工明确:cmd_sock 发命令并等待响应(同步),event_sock 接收内核主动推送的事件(异步)•为什么必须是两个 socket 而不是一个? Netlink 协议用序列号匹配请求和响应——HAL 发出 NL80211_CMD_TRIGGER_SCAN(序列号=42),内核返回 NL80211_CMD_SCAN_RESULTS(序列号=42),HAL 凭序列号识别这是第 42 号命令的响应。如果内核的异步事件(如 NL80211_CMD_DISASSOCIATED,由 AP 断连触发)和 HAL 的命令响应混在同一个 socket 上,异步事件可能恰好落在 HAL 等待第 42 号响应的窗口内,被误当成命令响应来解析——序列号对不上,HAL 的逻辑就会出错。两路分拆消除了这个竞态。•nl_connect(sock, NETLINK_GENERIC) 连接的不是传统网络地址,而是内核的 Generic Netlink 总线•genl_ctrl_resolve() 发起一次 CTRL_CMD_GETFAMILY 查询,询问内核"nl80211 的 family ID 是多少"——后续所有 nl80211 消息都需要带上这个 ID。注意这里传入的是局部变量 cmd_sock(而非 info->cmd_sock,虽然两者此时指向同一个对象)•wifi_add_membership() 的第一个参数是 (wifi_handle)info——将 hal_info* 指针转换为不透明的 wifi_handle 类型,函数内部通过 getHalInfo() 再转回来•四个组播组的含义:scan(扫描结果通知)、mlme(802.11 管理帧事件)、regulatory(频谱管制域变更)、vendor(厂商自定义事件)
异常路径提示:如果
genl_ctrl_resolve("nl80211")返回负数(内核未加载 cfg80211 模块、或不支持 nl80211 族),HAL 的wifi_initialize()会级联释放已分配的所有资源(nl_socket_free+free)并返回WIFI_ERROR_UNKNOWN。这个错误沿 HAL 调用链返回Wifi::startInternal()→Wifi::start()→ Binder 错误码 → Java 侧HalDeviceManager.startWifi()返回false→ 最终 WiFi 子系统停留在 DisabledState。这是一个不可恢复的硬件层错误——此时即使重试也毫无意义,因为内核根本没有提供 nl80211 接口。
8.3 ioctl 通道:HAL 和 Supplicant 如何与内核交互
除了 nl80211,WiFi 子系统还大量使用 ioctl 系统调用进行通信:
| wpa_supplicant | linux_ioctl.c | SIOCGIFFLAGSSIOCSIFFLAGS(up/down 接口)、SIOCGIFHWADDR/SIOCSIFHWADDR(MAC 地址)、SIOCBRADDBR/SIOCBRDELBR(网桥) | |
| wpa_supplicant | driver_cmd_nl80211.c | SIOCDEVPRIVATE + 1 | |
| QCOM HAL | wificonfig.cpp | SIOCGIFFLAGSSIOCSIFFLAGS(设置 IFF_UP) | |
| MTK HAL | wifi_hal.cpp | SIOCDEVPRIVATE + 1set_fw_param) | |
| QCOM 内核 HAL | wlan_hdd_wext.c | SIOCIWFIRSTPRIV + N | |
| MTK 内核 HAL | gl_wext.cgl_wext_priv.c | SIOCGIW*SIOCSIW*(ESSID、扫描、信道……)+ SIOCIWFIRSTPRIV + N |
关键理解:nl80211 和 ioctl 是两条互补的通道,不是竞争关系。nl80211 处理 cfg80211 框架定义的标准 WiFi 操作,ioctl 处理网络层操作(接口 up/down、MAC 地址)和厂商自定义扩展命令。HAL 和 Supplicant 同时使用两者。
8.4 QCOM 的额外通道:CLD80211
QCOM 的 HAL 比 MTK 多了一个特殊的通信通道——CLD80211:
// QCOM/hardware-qcom-wlan/qcwcn/wifi_hal/wifi_hal.cpp (wifi_initialize 中)// 创建 CLD80211 诊断通道info->cldctx = cld80211_init();if (info->cldctx != NULL) {// 成功:提取底层 nl_sock 并设置回调info->user_sock = cld80211_get_nl_socket_ctx(info->cldctx);if (info->user_sock == NULL) goto cld80211_cleanup;wifi_init_cld80211_sock_cb(info); // 设置 NL 回调(error/finish/ack/valid)// 订阅 4 个无条件组播组cld80211_add_mcast_group(info->cldctx, "host_logs");cld80211_add_mcast_group(info->cldctx, "per_pkt_stats");cld80211_add_mcast_group(info->cldctx, "diag_events");cld80211_add_mcast_group(info->cldctx, "fatal_events");// oem_msgs 需要 OEM 控制 socket 就绪if (info->wifihal_ctrl_sock.s > 0) {cld80211_add_mcast_group(info->cldctx, "oem_msgs");}} else {// CLD80211 不可用,回退到标准 NL socketret = wifi_init_user_sock(info);if (ret != WIFI_SUCCESS) goto unload;}// fw_logs 在函数末尾单独订阅(gated on cldctx != NULL)
主要功能:
•CLD80211 是 QCOM 私有的 Generic Netlink 族,用于固件日志和诊断数据的上报•成功路径(cldctx != NULL)有 2 个中间步骤:先通过 cld80211_get_nl_socket_ctx() 提取底层 nl_sock,再通过 wifi_init_cld80211_sock_cb() 设置 NL 回调(error/finish/ack/valid handler)•4 个无条件组播组:host_logs、per_pkt_stats、diag_events、fatal_events•oem_msgs 有条件包裹:仅当 OEM 控制 socket 已创建(wifihal_ctrl_sock.s > 0)时才订阅•fw_logs 在函数末尾单独订阅(不在主 cld80211 块内),同样 gated on cldctx != NULL•失败路径(cldctx == NULL)回退到 wifi_init_user_sock()——一个不依赖 cld80211 库的标准 NL socket•MTK 没有这个通道——它的日志和诊断信息通过标准 nl80211 vendor 事件上报
8.5 三条独立的 nl80211 通道
读到这里你可能已经注意到:系统的不同组件各自独立地创建 netlink socket。总结如下:
wpa_supplicant | driver_nl80211.c | ||
wificond | netlink_manager.cpp | ||
vendor.wifi_hal | wifi_hal.cpp |
它们没有共享 socket——每个进程独立 nl_socket_alloc() → nl_connect() → genl_ctrl_resolve("nl80211")。三个进程各自持有自己的 nl80211 family ID,各自独立向内核收发消息。
这种设计的好处是进程隔离:如果 wpa_supplicant 崩溃,vendor.wifi_hal 的 socket 不受影响;如果厂商 HAL 需要发 vendor 命令,它不需要经过 wpa_supplicant 中转。坏处是内核中的重复开销——三个进程各自维护 netlink 缓冲区,各自订阅组播组,内核需要向三个 socket 各自推送相同的事件。
九、HAL 就绪之后:驱动加载 & Supplicant 启动
前面八章追踪了从用户点击开关到 HAL 就绪的完整链路。到这里,HalDeviceManager.startWifi() 已经拉起了 vendor.wifi_hal 进程,HAL 已经初始化完毕。接下来有两件事要完成,而且有先后顺序:
1.第一步:驱动加载——HAL 进程内部通过 vendor .so 加载 WiFi 驱动,完成 PCIe 枚举、固件下载、WMI 握手2.第二步:Supplicant 启动——驱动就绪后,Framework 才启动 wpa_supplicant,它通过 nl80211 与已注册的 cfg80211 通信
9.1 第一步:驱动是怎么被加载的?
HalDeviceManager.startWifi() 调用 mWifi.start() 通过 AIDL Binder 跨进程调用 HAL 进程的 Wifi::startInternal()。Framework 的 Java 代码不会直接 insmod 驱动——驱动加载是 HAL 进程内部的 C++ 代码完成的。
调用链从 Framework 到 insmod:
HalDeviceManager.startWifi() // HalDeviceManager.java:1471→ WifiHal.start() // WifiHal.java:209→ WifiHalAidlImpl.start() // WifiHalAidlImpl.java:174──── AIDL Binder IPC(跨进程) ────→ Wifi::startInternal() // wifi.cpp:246→ initializeModeControllerAndLegacyHal() // wifi.cpp:361→ mode_controller_->initialize() // wifi_mode_controller.cpp→ driver_tool_->LoadDriver() // driver_tool.cpp:34→ ::wifi_load_driver() // wifi_hal_common.cpp:180→ insmod() // syscall(__NR_finit_module)
wifi_load_driver() 通过 insmod() 系统调用将 WiFi 驱动模块(.ko 文件)加载到内核。insmod() 内部调用 syscall(__NR_finit_module, fd, path, flags)——这是 Linux 内核提供的模块加载接口,用户空间的 C 代码可以通过它向内核注入模块,Java 代码做不到这一点。
加载驱动之后,HAL 还需要初始化 vendor HAL .so:
→ WifiLegacyHalFactory::getHals() // wifi_legacy_hal_factory.cpp:72→ dlopen("libwifi-hal-qcom.so") // 加载厂商 HAL 实现→ dlsym("init_wifi_vendor_hal_func_table") // 填充函数指针表→ WifiLegacyHal::start() // wifi_legacy_hal.cpp:558→ wifi_wait_for_driver_ready() // 等待驱动就绪→ wifi_initialize() // 获取全局句柄
insmod vs dlopen:
insmod加载的是内核模块(.ko),运行在内核空间;dlopen加载的是用户空间共享库(.so),运行在 HAL 进程中。WiFi 驱动加载涉及两者:先 insmod 内核驱动模块,再 dlopen vendor HAL .so 来与内核驱动通信。
9.2 第二步:Supplicant 是怎么被启动的?
驱动就绪后,ConcreteClientModeManager 的状态机收到 CMD_SWITCH_TO_CONNECT_MODE,开始启动 Supplicant。调用链:
ConcreteClientModeManager.ClientModeStateMachine // ConcreteClientModeManager.java:1077→ CMD_SWITCH_TO_CONNECT_MODE 处理→ WifiNative.switchClientInterfaceToConnectivityMode() // WifiNative.java:1749→ WifiNative.startSupplicant() // WifiNative.java:725→ WifiNative.startAndWaitForSupplicantConnection() // WifiNative.java:698→ SupplicantStaIfaceHal.initialize() // 检查 ISupplicant 服务是否已声明→ SupplicantStaIfaceHal.startDaemon() // SupplicantStaIfaceHal.java:1012→ SupplicantStaIfaceHalAidlImpl.startDaemon() // :492→ getSupplicantMockable() // :576→ ServiceManager.waitForDeclaredService("android.hardware.wifi.supplicant.ISupplicant/default")
ServiceManager.waitForDeclaredService() 是关键——它不会主动启动服务,而是等待服务出现。由于 .rc 文件中 wpa_supplicant 声明了 disabled(开机不自启动),Android 的 lazy HAL 机制会在客户端调用 waitForDeclaredService() 时通知 init 启动该服务:
# external_wpa_supplicant_8/wpa_supplicant/aidl/vendor/android.hardware.wifi.supplicant-service.rcservice wpa_supplicant /vendor/bin/hw/wpa_supplicant \-O/data/vendor/wifi/wpa/sockets -dd \-g@android:wpa_wlan0interface aidl android.hardware.wifi.supplicant.ISupplicant/defaultclass mainsocket wpa_wlan0 dgram 660 wifi wifidisabledoneshot
init 收到启动请求后,fork() + execve("/vendor/bin/hw/wpa_supplicant", ...) 启动进程,main() 被调用。main() 执行后,AIDL 层将 ISupplicant 服务注册到 ServiceManager,Framework 的 waitForDeclaredService() 返回,连接建立。
rc 文件语法速览:
service <名称> <可执行文件路径>定义一个 init 管理的服务。interface aidl ...声明该服务提供的 AIDL 接口(init 用它来响应 lazy HAL 启动请求)。disabled表示开机不自动启动,等被请求时才启动。oneshot表示进程退出后不自动重启。socket让 init 预创建一个 Unix domain socket 并传给服务进程。lazy HAL 机制:Android 的 lazy HAL 允许服务声明为"按需启动"。当客户端调用
ServiceManager.waitForDeclaredService()时,ServiceManager 检查服务是否已注册——如果没有,它通过init的属性系统触发服务启动。客户端阻塞等待直到服务注册完成。这种机制的好处是:不使用 WiFi 时 Supplicant 进程不占用内存和 CPU
驱动加载的详细分析(QCOM 6 阶段、MTK wlanProbe 12 步、SSR 崩溃恢复)见下一篇《驱动加载与崩溃恢复》。Supplicant 启动后的 eloop 事件循环、状态机、AIDL 接口层见后续《Supplicant 启动》。
常见错误速查表
WiFi 打不开是 Android 用户最常见的问题之一。下面这张表按调用链的顺序,列出了从点击开关到 HAL 就绪过程中所有可能的失败点、对应的日志关键字和排查思路。
CHANGE_WIFI_STATE 权限 | SecurityException: WifiService | |||||
"setWifiEnabled not allowed for uid=" | ||||||
"setWifiEnabled not allowed as satellite mode is on." | ||||||
"setWifiEnabled in Airplane mode: only Settings can toggle wifi" | ||||||
"setWifiEnabled with SoftAp enabled" | ||||||
"setWifiEnabled with user restriction: only DO/PO can toggle wifi" | ||||||
"setWifiEnabled must show user confirmation dialog for uid=%" | ||||||
handleWifiToggled | AIRPLANE_MODE_TOGGLEABLE_RADIOS | |||||
ERROR_NOT_AVAILABLE(STOPPING 状态) | "Cannot start wifi because unavailable. Retrying..." | |||||
"Cannot start IWifi after trying 3 times" | vendor.wifi_hal | |||||
"Cannot start IWifi. Status: <code>" | ||||||
vendor.wifi_hal | "No HIDL or AIDL service available for the Wifi Vendor HAL." | init.rc 和 HAL 进程状态 | ||||
"failed to open vendor hal library: <path>" | ldd 检查) | |||||
"init_wifi_vendor_hal_func_table not found in: <path>" | ||||||
genl_ctrl_resolve("nl80211") | CONFIG_CFG80211 未开启或模块未加载(lsmod | grep cfg80211) | |||||
"Failed to get IClientInterface" | wificond 进程状态 | |||||
"Failed to initialize vendor HAL" |
日志级别说明:
info级别的拒绝日志在默认 logcat 中可见;err级别的日志会更显眼。调试时建议logcat -s WifiService:* WifiVendorHal:* HalDeviceManager:*。
总结:从点击开关到 HAL 就绪,一条线串起来
这篇文章追踪了一条完整的调用链。让这张表帮你回顾整个过程:
WifiManager | |||
WifiServiceImpl | |||
ActiveModeWarden | wifiToggled() | ||
WifiController | DisabledStateEnabledState 转移 | ||
ConcreteClientModeManager | ClientModeImpl(9 状态 FSM) | ||
HalDeviceManager | IWifi.start() 拉起 HAL 进程 | ||
WifiLegacyHalFactory | dlopendlsym 加载厂商 .so | ||
nl_socket_alloc()genl_ctrl_resolve("nl80211") |
此时 HAL 已就绪,nl80211 通道已建立,ClientModeImpl 正处于 DisconnectedState 等待连接指令。接下来的事情——wpa_supplicant 的初始化、扫描、关联、DHCP——这些属于"从 HAL 就绪到连接成功"的下半场。我们下一章继续。
下一章预告
Android WiFi 源码分析(三):驱动加载 — QCOM 与 MTK 怎么把芯片叫醒?
•QCOM 平台驱动加载的 6 个阶段:从 module_init 到 wiphy_register•ICNSS2 集成 WiFi 路径(WCN6750/WCN7750)与 cnss2 PCIe 路径的差异•MTK 平台驱动加载的完整流程:conninfra 共享框架 → wlanProbe → FW Ready 位轮询•wlan_objmgr 三层对象模型的创建机制和组件回调模式•WMI 握手的三个事件(Service Ready / EXT / EXT2)各自携带什么信息•SSR 崩溃恢复的三级体系(QCOM)和两级体系(MTK)
References
[1] packages/modules/Wifi: https://android.googlesource.com/platform/packages/modules/Wifi/[2] hardware/interfaces/wifi: https://android.googlesource.com/platform/hardware/interfaces/[3] hardware/qcom/wlan/qcwcn/wifi_hal: https://github.com/nicklela21/hardware_qcom_wlan[4] hardware/mediatek/wlan/wifi_hal: https://github.com/nicklela21/hardware_mediatek
夜雨聆风