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

小白记住三句话:

  1. Settings 不做任何真正的 WiFi 操作——扫描、连接、握手全在 WifiService/wpa_supplicant;Settings 只发请求(方法调用)和收通知(广播/回调)。
  2. 中间的 WifiTrackerLib 是”翻译官”:把框架一堆异步信号翻译成”一组 WifiEntry + 一次 onWifiEntriesChanged 回调”给 UI 用。
  3. 所以一切 UI 时序问题的本质 = “框架事件到达顺序 × 库的合并窗口 × UI 的重建策略”三者叠加。后面每篇都在拆这三个变量。

1. WifiManager:唯一的”遥控器”

Settings 侧所有对 WiFi 的操作都汇到 android.net.wifi.WifiManager(Binder → WifiService)。车机仓库里的实际调用清单(全量 grep 核实):

操作调用点(file:line)说明
setWifiEnabledMiCarWifiUtils.kt:172(全仓库唯一)WLAN 总开关,上游只有 WifiSwitchController.kt:114
startScanWifiTrackerLib 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 / getConnectionInfoWifiPickerTracker.java:1252 / :1353 / :404tracker 拉数据
getWifiStateMiCarWifiUtils.kt:175-177开关态查询

2. 四大系统广播(库的生命线)

BaseWifiTracker.onStart()(f3dif BaseWifiTracker.java:551-628)注册的广播族(worker 线程接收,:614-615 registerReceiver(..., mWorkerHandler, RECEIVER_EXPORTED)):

广播 action谁发触发库内对 UI 的意义
WIFI_STATE_CHANGED_ACTIONWifiService(开关翻转)handleWifiStateChangedAction(WifiPickerTracker.java:356-363)开关回位/列表清空
SCAN_RESULTS_AVAILABLE_ACTIONWifiService(一轮扫描完成)handleScanResultsAvailableAction(:365-373)列表内容刷新的主心跳
NETWORK_STATE_CHANGED_ACTIONWifiService(连接状态变化)handleNetworkStateChangedAction(:397-434)条目 CONNECTING/CONNECTED
CONFIGURED_NETWORKS_CHANGED_ACTIONWifiService(保存配置增删改)processConfiguredNetworksChanged(:377-393)isSaved 翻转(忘网/新连)

另有 ConnectivityManager 的 NetworkCallback(TRANSPORT_WIFI+NOT_VPN,BaseWifiTracker.java:617-618):onCapabilitiesChangedL3 通了的唯一权威信号——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 reasonbugreport 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、状态机。