20 · WiFi 框架全景:车机 Settings 站在哪一层
适用范围:把 10-基础概念 的”无线电世界”落到 Android 的进程与类上——谁提供 API、谁发广播、Settings(以及它带的 WifiTrackerLib)在中间扮演什么角色。 代码基线:
/home/zbc/car/fuben_micarsettings/MiCarSettings@ dev,f3 flavor。本文论断全部有 file:line 出处。
0. 分层地图(先背这张图)
flowchart TB subgraph APP["Settings App 进程 (com.android.car.settings)"] UI["WLAN 页面<br/>Fragment/Controller/Preference"] TL["WifiTrackerLib (vendored 库)<br/>base/WifiTrackerLib/src/f3dif<br/>~26 文件 12547 行"] MU["MiCarWifiUtils / MiWifiTrackerUtils<br/>WifiManager 薄封装"] end subgraph SYS["system_server 进程"] WS["WifiService (+WifiServiceImpl)"] CS["ConnectivityService"] end subgraph SUPP["native/独立进程"] WPA["wpa_supplicant<br/>握手/认证状态机"] end HW["WLAN 驱动+固件+芯片"] UI -->|"读写 UI 态"| TL TL -->|"startScan/connect/forget<br/>getScanResults/getConfiguredNetworks"| WS MU -->|"connect(WifiConfiguration)<br/>setWifiEnabled/allowAutojoin"| WS TL -->|"NetworkCallback<br/>(onCapabilitiesChanged/onLost)"| CS WS --> WPA --> HW WS -.->|"广播: SCAN_RESULTS_AVAILABLE /<br/>NETWORK_STATE_CHANGED /<br/>WIFI_STATE_CHANGED /<br/>CONFIGURED_NETWORKS_CHANGED"| TL CS -.->|"VALIDATED 等网络可用性"| TL
小白记住三句话:
- Settings 不做任何真正的 WiFi 操作——扫描、连接、握手全在 WifiService/wpa_supplicant;Settings 只发请求(方法调用)和收通知(广播/回调)。
- 中间的 WifiTrackerLib 是”翻译官”:把框架一堆异步信号翻译成”一组 WifiEntry + 一次 onWifiEntriesChanged 回调”给 UI 用。
- 所以一切 UI 时序问题的本质 = “框架事件到达顺序 × 库的合并窗口 × UI 的重建策略”三者叠加。后面每篇都在拆这三个变量。
1. WifiManager:唯一的”遥控器”
Settings 侧所有对 WiFi 的操作都汇到 android.net.wifi.WifiManager(Binder → WifiService)。车机仓库里的实际调用清单(全量 grep 核实):
| 操作 | 调用点(file:line) | 说明 |
|---|---|---|
setWifiEnabled | MiCarWifiUtils.kt:172(全仓库唯一) | WLAN 总开关,上游只有 WifiSwitchController.kt:114 |
startScan | WifiTrackerLib BaseWifiTracker.java:1144(Scanner 循环)+ MiCarWifiUtils.kt:157-159(下拉刷新/开关刚开) | 两条扫描入口 |
connect(networkId) | StandardWifiEntry.java:534 | 已保存网静默直连 |
connect(WifiConfiguration) | MiCarWifiUtils.kt:51-69(密码框路径) | 新密码连接 |
forget(networkId) | StandardWifiEntry.java:646 | 忘记网络 |
allowAutojoin(networkId, enabled) | StandardWifiEntry.java:820-826 | 详情页”自动连接”开关 |
getScanResults / getConfiguredNetworks / getConnectionInfo | WifiPickerTracker.java:1252 / :1353 / :404 | tracker 拉数据 |
getWifiState | MiCarWifiUtils.kt:175-177 | 开关态查询 |
2. 四大系统广播(库的生命线)
BaseWifiTracker.onStart()(f3dif BaseWifiTracker.java:551-628)注册的广播族(worker 线程接收,:614-615 registerReceiver(..., mWorkerHandler, RECEIVER_EXPORTED)):
| 广播 action | 谁发 | 触发库内 | 对 UI 的意义 |
|---|---|---|---|
WIFI_STATE_CHANGED_ACTION | WifiService(开关翻转) | handleWifiStateChangedAction(WifiPickerTracker.java:356-363) | 开关回位/列表清空 |
SCAN_RESULTS_AVAILABLE_ACTION | WifiService(一轮扫描完成) | handleScanResultsAvailableAction(:365-373) | 列表内容刷新的主心跳 |
NETWORK_STATE_CHANGED_ACTION | WifiService(连接状态变化) | handleNetworkStateChangedAction(:397-434) | 条目 CONNECTING/CONNECTED |
CONFIGURED_NETWORKS_CHANGED_ACTION | WifiService(保存配置增删改) | processConfiguredNetworksChanged(:377-393) | isSaved 翻转(忘网/新连) |
另有 ConnectivityManager 的 NetworkCallback(TRANSPORT_WIFI+NOT_VPN,BaseWifiTracker.java:617-618):onCapabilitiesChanged 是 L3 通了的唯一权威信号——getConnectedState()==CONNECTED 就靠它(WifiEntry.java:328-330)。
小白时序直觉:
- 广播是”进程间的一封信”,路上要过 Binder + Handler 队列,不保证多条广播的先后等于真实事件先后;
- 库收到每封信都要重新拉数据(binder)重算条目,再 post 主线程通知 UI——一次真实事件 = 2~3 次 UI 重建很正常(忘网场景实测,见 34-忘记网络)。
3. WifiTrackerLib:为什么 Settings 要自带一个库
问题:框架只给你”原始素材”(一堆 ScanResult、一串广播),UI 要的是”一行一行的网络条目 + 状态”。
解法:Google 把这个翻译层做成了可 vendor 的库(packages/apps/Settings 的 WifiTrackerLib),车机整套拷进 base/WifiTrackerLib/,并按平台分了三份源集(f3dif/xcddif/dcddif,详见 22-flavor 差异)。
库对外只暴露两个东西:
WifiPickerTracker(及同类 tracker)——数据引擎;WifiEntry——一个网络的门面对象(SSID/level/security/connectedState/connect/forget…)。
车机重要事实:正常 WLAN UI 由每页一份 tracker 驱动——列表页(MiCarWifiListFragment.kt:74)与开机引导页(MiCarWifiProvisionFragment.kt:46)各创建一个 WifiPickerTracker(经 MiWifiTrackerUtils → MiCarWifiCollinear.createWifiPickerTracker);NetworkDetailsTracker/SavedNetworkTracker 在 Settings 侧零引用(全仓 grep)——详情页直接持有列表页的同一个 WifiEntry 对象引用(MiCarWifiUtils.detailWifiEntry 静态传递,MiCarWifiEntryDetailFragment.kt:47-51)。这与 AOSP 手机版”详情页自建 NetworkDetailsTracker”是最大架构差异,详见 21-架构篇 §7。
4. 一轮完整扫描的心跳时序
sequenceDiagram participant S as Scanner(库内,worker) participant WS as WifiService participant B as mBroadcastReceiver(worker) participant P as WifiPickerTracker(worker) participant M as mainHandler participant C as Controller(UI) Note over S: 页面Started+WiFi开+屏亮 才跑<br/>10s 一轮(scanLoop:1119-1153) S->>WS: WifiManager.startScan()(:1144) WS-->>B: SCAN_RESULTS_AVAILABLE 广播 B->>P: handleScanResultsAvailableAction(:365) P->>WS: getScanResults()(:1252) P->>P: ScanResultUpdater 合并去重<br/>(15s 过期/失败5min兜底) P->>P: updateStandardWifiEntryScans(:879)<br/>建/复用 entry,UNREACHABLE 移除 P->>P: updateWifiEntries(:609)<br/>拆 active/wifiEntries 快照 P->>M: notifyOnWifiEntriesChanged(:1554,post主线程) M->>C: onWifiEntriesChanged → refreshUi C->>C: 子线程fillWifiEntries→主线程<br/>removeAll+全量重建
代码调用链(对照上图):
BaseWifiTracker$Scanner#scanLoop:1119 (worker, 10s postDelayed 自驱)
→ WifiManager.startScan:1144
→ [框架广播 SCAN_RESULTS_AVAILABLE_CHANGED]
→ BaseWifiTracker$mBroadcastReceiver#onReceive:155-204 (worker 线程)
→ WifiPickerTracker#handleScanResultsAvailableAction:365-373
EXTRA_RESULTS_UPDATED → conditionallyUpdateScanResults(poll, timeout):1231-1282
→ mWifiManager.getScanResults():1252
→ ScanResultUpdater#onScanResultsAvailable:59-73 (SSID+BSSID 去重/过期裁剪)
→ updateStandardWifiEntryScans:879-940 (按 ScanResultKey 复用/新建 entry)
→ WifiPickerTracker#updateWifiEntries:609-791 (排序,拆 mActiveWifiEntries/mWifiEntries)
→ notifyOnWifiEntriesChanged:1554-1558 (mMainHandler.post)
→ MiWifiTrackerUtils$mTrackerListener#onWifiEntriesChanged:51-55 (扇出)
→ MiCarBaseWifiEntryController#onWifiEntriesChanged → refreshUi
→ updateState(preference):96-120 (SINGLE_ASYNC fill → 主线程 UPDATE_MESSAGE_WHAT)
→ 子类 updateState(pref, list): removeAll + 重建5. MIUI 定制点速览(读 f3dif 代码时的”非 AOSP”标记)
| 定制 | 位置 | 效果 |
|---|---|---|
| AOSP WifiScanner 首快扫整段禁用 | BaseWifiTracker.java:1084-1109(MIUI DEL: WIFI_ScanControll) | 首扫也走 WifiManager.startScan() |
| 扫描 gate 加屏幕亮 | shouldScan() :1073-1076(PowerManager.isInteractive()) | 灭屏不扫(xcddif 无此 gate) |
| handleOnStart 不裁旧扫描 | WifiPickerTracker.java:346-349(timeoutScans=false) | 回页面瞬间不清列表 |
| >200 条扫描截断 | WifiPickerTracker.java:1256-1264 | 防 binder 超限 |
| 信号格数云控阈值 | WifiEntryUtilsStub:43-70(2.4G{-91,-77,-65}/5G{-91,-79,-65}) | Settings.System cloud_wifi_signal_level 可覆盖 |
| 双 WiFi(slave)/MLO/PasspointR1 桩 | SlaveWifiUtilsStub/PasspointUtilsStub(反射注入) | 车机上事实性休眠(worker 线程名不含 “slave”,详见 22 篇 §3) |
| WAPI 安全类型 | Security 常量 :113-127(WAPI_PSK=7/WAPI_CERT=8) | App 层 getWifiConfig 对 WAPI 返回 null 防崩(MiCarWifiUtils.kt:147-152) |
| connectedWifiEntry 取 active 首个 | WifiPickerTracker.java:776-787(去掉 isPrimaryNetwork 校验) | 不再区分主副网络 |
6. 排障视角:三层各查什么
| 层 | 查什么 | 工具 |
|---|---|---|
| 框架层 | 开关/扫描/连接的真实时序、throttle、disconnect reason | bugreport logcat(WifiService/wpa_supplicant tag)、dumpsys wifi |
| 库层 | 条目建/删/状态翻转、合并窗口 | WifiTracker verbose log(adb shell setprop log.tag.WifiPickerTracker VERBOSE)、WifiEntry.toString(:1557-1616) |
| App 层 | 收到几波回调、removeAll 时机、空波防护 | MiCarWifiUtils/Controller 的 MLog、dumpsys activity |
三层对不上的案例:131987(框架空扫描 × App 无空保护)、2782(框架 scan throttle × 库 15s 窗口)、3907(框架层 SAE 关联后被踢,App 无责)——见 40-排障篇。
下一篇:21-WifiTrackerLib 架构——把本图的”翻译官”拆开:双线程模型、条目缓存、去重 key、状态机。