WLAN / WiFi 全流程
模块根:
settingsPage/micarConnectionSettings/src/main/java/com/android/car/settings/miauto/wifi/
0. 数据源与库定位(最关键)
WifiEntry/WifiPickerTracker来自仓库内自带com.android.wifitrackerlib(base/WifiTrackerLib/),非 AOSPcom.android.wifitracker2。- 依赖链:
micarConnectionSettings/build.gradle:82api project(":base:settingsBaseLib")→base/settingsBaseLib/build.gradle:92api project(":base:WifiTrackerLib") WifiPickerTracker在base/WifiTrackerLib/src/{xcddif,dcddif}/java/com/android/wifitrackerlib/(两套 flavor)
- 依赖链:
- 工厂
createWifiPickerTracker(...)是 flavor-specific:src/{xcddif,dcddif}/.../wifi/MiCarWifiCollinear.kt:17/:20,调用点MiWifiTrackerUtils.kt:65 - 扫描参数:
MiCarWifiUtils.DEFAULT_SCAN_INTERVAL_MILLIS = 10_000L(:19),MiWifiTrackerUtils.DEFAULT_MAX_SCAN_AGE_MILLIS = 15_000L(:29) - 回调链:
WifiPickerTracker.WifiPickerTrackerCallback.onWifiEntriesChanged/onWifiStateChanged→MiWifiTrackerUtils.mTrackerListener:44-64→ 各 Controller
1. 架构总览
分层:View(Fragment/Activity) 持有 MiWifiTrackerUtils(数据源) → Controller(业务) 通过 InstanceGetter 拿 tracker → 驱动 Preference(UI)。
classDiagram class MiWifiTrackerUtils { -WifiPickerTracker mWifiTracker +getAllWifiEntries() +getSavedWifiEntries() +getConnectedWifiWifiEntry()} class MiCarBaseWifiEntryController {<<abstract>> #CopyOnWriteArrayList mAllWifiEntries +fillWifiEntries()* +updateState()*} class MiCarAvailableWifiListController class MiCarSavedWifiListController class MiCarProvisionWifiListController class MiCarWifiUtils { +setWifiEnable() +connect() +startScan()} class WifiSwitchController class WifiConnectionStateController WifiPickerTracker <|.. MiWifiTrackerUtils : wraps MiWifiTrackerUtils o-- MiCarBaseWifiEntryController : Listener MiCarBaseWifiEntryController <|-- MiCarAvailableWifiListController MiCarBaseWifiEntryController <|-- MiCarSavedWifiListController MiCarBaseWifiEntryController <|-- MiCarProvisionWifiListController
2. 开关与状态
WLAN 开关(WifiSwitchController)
handlePreferenceChanged:101:防双击isQuickDoubleClick(:107)→ 真变化时ThreadPoolUtils.ASYNC.execute { MiCarWifiUtils.setWifiEnabled(it) }(:113-115)→setLoading(true)- 底层
MiCarWifiUtils.setWifiEnable:147-149→mWifiManager?.setWifiEnabled(全仓唯一点) - 回调
onWifiStateChanged:74:仅WIFI_STATE_ENABLED/DISABLED才更新 UI 并Trace.endSection(),中间态保持 loading
stateDiagram-v2 [*] --> DISABLED DISABLED --> ENABLING : setWifiEnabled(true) ENABLING --> ENABLED : onWifiStateChanged(ENABLED) ENABLED --> DISABLING : setWifiEnabled(false) DISABLING --> DISABLED : onWifiStateChanged(DISABLED) note right of ENABLING: toggle 显示 loading note right of ENABLED: 列表可见, startScan()
连接主页 WLAN 入口状态(WifiConnectionStateController)
不依赖 WifiPickerTracker,直接注册 BroadcastReceiver(:39-65)监听 WIFI_STATE_CHANGED_ACTION + NETWORK_STATE_CHANGED_ACTION。refreshWifiEntrySummary:82:WiFi 关→wifi_state_off_summary;开但 SSID 未知→wifi_state_on_summary;已连接→取 WifiInfo.ssid 去 getRealSsid 引号(:117-123);Passpoint→passpointProviderFriendlyName。异步取数据主线程刷 updateRightText。
3. 扫描与列表
数据来源(MiWifiTrackerUtils.getWifiEntries:105)
HotspotUtils.refreshHotspotInfo()先刷本地热点信息- WiFi 关→返回空
- 遍历
mWifiTracker.wifiEntries过滤:isHotspot(本地热点不算)排除、level==UNREACHABLE排除、按 isOnlyNeedSaved 筛选
三个列表差异(关键:可用与已保存互斥)
| Controller | 数据源 | 已连接 | 已保存 |
|---|---|---|---|
MiCarAvailableWifiListController.fillWifiEntries:50 | getAllWifiEntries | 仅计 mStartIndex 不显示 | 显式跳过 isSaved(:52-58) |
MiCarSavedWifiListController.fillWifiEntries:73 | getConnected + getSaved | 头部 | 是 |
MiCarProvisionWifiListController.fillWifiEntries:47 | getConnected + getAll | 头部 | 不区分 |
互斥设计:同一 WiFi 不会同时出现在两栏。available 显式跳过
isSaved,隐含 UX 约定——已保存但未连接的只在”已保存”栏。
刷新触发
- 自动扫描:
WifiPickerTracker内部 Scanner 按 10s 周期 - 手动:
MiCarAvailableWifiListController.startScan:70→MiCarWifiUtils.startScan:134(唯一点),触发器=下拉刷新 / WiFi 刚 ENABLED - UI 节流:
MiCarBaseWifiEntryController.updateState:87-111用removeMessages + SINGLE_ASYNC.remove避免抖屏;扫描动画 10s 后强制停(:73-74)
sequenceDiagram participant Tracker as WifiPickerTracker participant Utils as MiWifiTrackerUtils participant Ctrl as MiCarAvailableWifiListController participant UI as Preference列表 Note over Tracker: 周期10s 或 startScan() Tracker->>Utils: onWifiEntriesChanged() Utils->>Ctrl: Listener.onWifiEntriesChanged() Ctrl->>Ctrl: refreshUi→updateState 调度 SINGLE_ASYNC Note over Ctrl: 子线程 fillWifiEntries 过滤 Ctrl->>UI: mHandler sendMessage removeAll+createPreference
4. 连接流程
点击入口(基类 MiCarBaseWifiEntryController.initWifiEntryPreference:162)
- 防双击(:166)
- 已连接且
canSignIn(captive portal)→signIn(null)(:172) - 未连接:
tryConnectWifiEntry = wifiEntry(标记重试对象)→wifiEntry.connect(WifiEntryConnectCallback)(:177-178)
回调与密码框(WifiEntryConnectCallback.onConnectResult:237)
CONNECT_STATUS_FAILURE_NO_CONFIG(首次需密码)→WifiPasswordDialog.show(:247)CONNECT_STATUS_FAILURE_UNKNOWN→ 仅日志- 其他 →
gotoTopPosition()
密码框 → WifiManager.connect
WifiPasswordDialog.show:49:EAP 显示 identity 框;密码默认隐藏;校验 WIFI_PWD_LENGTH_RANGE=8..63(:25)+ identity 非空。点连接→MiCarWifiUtils.connect:52(WifiManager.connect 唯一点)→ getWifiConfig:66-131 按 security 分支(NONE/WEP/PSK/SAE/OWE/EAP/EAP_SUITE_B)。
已保存免密直连
已保存网络 wifiEntry.connect 时 wifitrackerlib 直接用已保存 WifiConfiguration 走 connect(networkId),不触发 NO_CONFIG,不弹密码框。
详情页(MiCarWifiEntryDetailFragment)
- 改密码:开放网络禁用(:104)→ WifiPasswordDialog → connect
- 删除:
confirmDeleteAp→forget(null)(:157,forget 唯一点)
连接失败重试广播
ACTION_MICAR_WIFI_CONNECTION_FAILURE = "micar.intent.action.WIFI_CONNECTION_FAILURE"(:20)。本仓不发送,由 framework 发(带 EXTRA_WIFI_CONFIGURATION)。接收方 WifiSettingsActivity.mConnectFailureReceiver:42 + WlanProvisionDialogActivity.mConnectFailureReceiver:50,匹配 SSID 后重弹密码框(isShowPasswordWrong=true)。
sequenceDiagram participant U as 用户 participant Base as MiCarBaseWifiEntryController participant Entry as WifiEntry participant Dia as WifiPasswordDialog participant Utils as MiCarWifiUtils participant Mgr as WifiManager U->>Base: 点击 WiFi 项 Base->>Entry: connect(callback) alt 有保存配置 Entry->>Mgr: connect(savedConfig) else 无配置(首次) Entry-->>Base: onConnectResult(NO_CONFIG) Base->>Dia: WifiPasswordDialog.show() U->>Dia: 输入密码 Dia->>Utils: connect(wifiEntry,pwd) Utils->>Mgr: connect(config, listener) Note over Mgr: 失败时 framework 发 WIFI_CONNECTION_FAILURE Mgr->>Base: (广播)重弹密码框 end
5. NetworkRequest 弹窗体系(独立子模块)
触发:App 通过 ConnectivityManager#requestNetwork 申请网络时,系统发 com.android.settings.wifi.action.NETWORK_REQUEST,命中 NetworkRequestDialogActivity(manifest:143-151,权限限定系统调用)。
与普通列表根本差异:不走 WifiPickerTracker/WifiEntry,直接用 WifiManager.NetworkRequestMatchCallback+ScanResult;用户选择调 NetworkRequestUserSelectionCallback.select(config)/reject(),由系统完成连接。单独 taskAffinity、singleTop、excludeFromRecents。
| Fragment | 职责 | 用户操作 |
|---|---|---|
NetworkRequestDialogBaseFragment | 抽象基类,提供标题/summary 钩子 | — |
NetworkRequestDialogFragment | 多 SSID 列表(默认模式) | selectNetwork→select(config) |
NetworkRequestSingleSsidDialogFragment | 单 SSID 确认(EXTRA_IS_SPECIFIED_SSID=true) | “连接”→select(config) |
NetworkRequestErrorDialogFragment | 错误/超时(onAbort/30s 无结果) | 重试/取消→reject() |
Activity 要点(NetworkRequestDialogActivity.java):onResume:73 注册 callback + 30s timeout;onMatch:175 按 specifiedSsid 分流;success/failure 都 setResult OK(仅文案不同)。
NetworkRequest 子包完全不引用 MiWifiTrackerUtils/WifiEntry/MiCarWifiUtils,新增功能勿复用普通列表 Preference/Controller。配置构建
NetworkRequestWifiConfigUtils.java:11从ScanResult.capabilities判加密,与 wifitrackerlib 完全解耦。
6. Provision 开机引导
触发(WifiSettingsActivity.onCreate:93-110):
val isProvisioned = DevelopmentSettingsUtil.isDeviceProvisioned(applicationContext)
val launchProvisionPage = !isProvisioned && refer != SETTINGS_CONNECTION_WIFI即系统未完成开机引导且非从连接主页来→startActivity(WlanProvisionDialogActivity)+finish。WlanProvisionDialogActivity manifest action com.android.car.settings.provision.WIFI_SETTINGS(:160,priority=100,singleInstance)。
与正常列表差异:
| 维度 | 正常列表 | 引导页 |
|---|---|---|
| Fragment 基类 | DialogSettingsFragment | TopLevelSettingsFragment |
| 密码框 | 非全屏、无取消键 | 全屏 + 带取消键 |
| ”继续”按钮 | 无 | 仅 hasConnectedAp=true 可用 |
| 语音序号 | 显示 | 隐藏 |
| 列表填充 | saved/available 分流 | connected + 所有(不区分) |
切换:isDeviceProvisioned 返回 true 后下次进正常 Fragment;MiCarWifiEntryPreference.mIsProvisioned:42 构造时取值,生命周期内不刷新(需列表重建)。
7. 设计要点与坑
- 扫描节流/防抖:tracker 10s 自动扫;UI updateState 用 removeMessages 防堆叠;手动扫描动画 10s 强制停防 loading 卡死;WiFi 关时直接 removeAll 不触发异步。
- 密码框安全:默认隐藏;8..63 长度(与 AOSP 一致);全屏隐藏 systemBars;
isShowing判重防重复弹。 - Provision 切换隐患:用
refer != SETTINGS_CONNECTION_WIFI区分入口,调用方漏传 refer 则未 provisioned 也进正常列表(可能非期望)。 - 单例隐患:
MiCarWifiUtils.detailWifiEntry/tryConnectWifiEntry(:31,36)是 object 字段跨 Fragment 传递。WifiSettingsActivity(singleTask) 与 WlanProvisionDialogActivity(singleInstance) taskAffinity 不同,并发覆盖会错乱。onDestroy 清 tryConnectWifiEntry,但 detailWifiEntry 永不清。 - 双平台:
createWifiPickerTracker与base/WifiTrackerLib均分 dcddif/xcddif 两套,改 WiFi 逻辑两套都要改。 - 连接失败广播隐性约定:
ACTION_MICAR_WIFI_CONNECTION_FAILURE由 framework 发,改时需同步确认 framework 侧(packages/modules/Wifi)仍发该 action、是否带EXTRA_WIFI_CONFIGURATION,否则重试链路失效。 - NetworkRequest 与普通列表解耦:两套完全独立,勿强行复用。
关键调用点速查
| 操作 | 位置 |
|---|---|
| WifiManager.setWifiEnabled | MiCarWifiUtils.kt:148(唯一点) |
| WifiManager.startScan | MiCarWifiUtils.kt:134(唯一点) |
| WifiManager.connect(config) | MiCarWifiUtils.kt:52(唯一点) |
| WifiEntry.connect | MiCarBaseWifiEntryController.kt:178 |
| WifiEntry.forget | MiCarWifiEntryDetailFragment.kt:157(唯一点) |
| NetworkRequestUserSelectionCallback.select/reject | NetworkRequestDialogActivity.java:227,249 |