21 · WifiTrackerLib 架构:双线程、条目缓存与状态机
核心文件:
base/WifiTrackerLib/src/f3dif/java/com/android/wifitrackerlib/下的BaseWifiTracker.java(1200 行)、WifiPickerTracker.java(1634 行)、WifiEntry.java(1741 行)、StandardWifiEntry.java、ScanResultUpdater.java(84 行) 代码基线:fuben_micarsettings @ dev,f3dif 源集(A17 车机在用);行号均指该源集 关联:20-框架全景(本库在系统里的位置)、22-flavor 差异
0. 一图流:数据怎么从”空中包”变成”屏幕上一行 WiFi”
[WifiService] ──广播──> BaseWifiTracker.mBroadcastReceiver(worker线程)
│ 按 action 分发 handleXxx()
▼
WifiPickerTracker.handleScanResultsAvailableAction()
│ conditionallyUpdateScanResults()
│ ├─ mWifiManager.getScanResults() 拉全量
│ ├─ mScanResultUpdater 合并(SSID+BSSID去重/过期裁剪)
│ └─ updateStandardWifiEntryScans() 建/复用 StandardWifiEntry
▼
updateWifiEntries() → 拆 mActiveWifiEntries(已连) / mWifiEntries(未连)
│ mMainHandler.post(...)
▼(主线程)
WifiPickerTrackerCallback.onWifiEntriesChanged(reason)
▼
MiWifiTrackerUtils.Listener → MiCarBaseWifiEntryController.refreshUi()1. BaseWifiTracker:骨架 = 双线程 + 广播分发 + 扫描循环
1.1 双线程模型(类注释 :89-112 原文)
* This class runs on two threads:
* The main thread
* - Processes lifecycle events (onStart, onStop)
* - Runs listener callbacks
* The worker thread
* - Drives the periodic scan requests
* - Handles the system broadcasts to update the API return values
* - Notifies the listener for updates to the API return valuesmMainHandler/mWorkerHandler均为构造注入(:466-477,字段 :218-219)。- 库内没有 mWorkerThread——车机侧由 Settings 建:
MiWifiTrackerUtils.kt:40-43起HandlerThread(TAG+{hash}, THREAD_PRIORITY_BACKGROUND),onDestroy时quit()(:136-138)。 - 构造还做:
new ScanResultUpdater(clock, maxScanAgeMillis)(:518)、new Scanner(workerHandler.getLooper())(:519)、lifecycle.addObserver延迟 post(:525-527,防同步回调 NPE)。
1.2 onStart 注册清单(全量)
onStart()(:551-628)按下表注册,全部回调落 worker 线程(广播 :614-615 registerReceiver(..., mWorkerHandler, RECEIVER_EXPORTED),MIUI 加的 EXPORTED;filter setPriority(SYSTEM_HIGH_PRIORITY) :595):
| 类型 | 对象 | 条件 | 处理 |
|---|---|---|---|
| 广播 | WIFI_STATE_CHANGED_ACTION | 非 Baklava+ 路径(:572-573) | handleWifiStateChangedAction(:171) |
| listener | addWifiStateChangedListener(WifiSCListener :352-377) | SDK>35 且开关打开(Injector:186-188) | 同上;注册后立刻 post 一次(:566-570) |
| listener | verbose 状态 listener(:574-575) | 总是 | 缓存 verbose 开关 |
| 广播 | SCAN_RESULTS_AVAILABLE_ACTION(:587) | 总是(MIUI 删了 !mIsScanningDisabled 门控) | handleScanResultsAvailableAction(:176) |
| 广播 | com.qualcomm.qti.net.wifi.PARTIAL_SCAN_RESULTS(:121-122,高通) | 总是(:608) | 同上(:173-176) |
| 广播 | CONFIGURED_NETWORKS_CHANGED_ACTION(:589) | 总是 | handleConfiguredNetworksChangedAction(:177-178) |
| 广播 | NETWORK_STATE_CHANGED_ACTION(:590) | 总是 | handleNetworkStateChangedAction(:179-180) |
| 广播 | RSSI_CHANGED_ACTION(:591-593) | 仅 verbose 开 | handleRssiChangedAction(:181-182) |
| 广播 | ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED(:594) | 总是 | handleDefaultSubscriptionChanged(:183-185) |
| 广播 | MIUI 双 WiFi 5 个 slave action(SlaveWifiUtilsStub.java:38-44;注册 :604-609) | 总是 | 仅 mIsSlave 时生效(:187-202) |
| CM | registerNetworkCallback(TRANSPORT_WIFI+NOT_VPN, FLAG_INCLUDE_LOCATION_INFO, worker)(:617-618) | 总是 | onLinkPropertiesChanged/onCapabilitiesChanged/onLost(:238-282,带主从/MLO 过滤) |
| CM | registerDefaultNetworkCallback(worker)(:619-620) | 总是 | mDefaultNetworkCallback(:284-298) |
| CDM | ConnectivityDiagnostics(:621-622) | 总是 | handleConnectivityReportAvailable(弱引用 :301-319) |
| SCM | SharedConnectivity(:623-627) | U+ 且服务存在 | handleKnownNetworksUpdated 等 8 回调(:386-431) |
⚠️ SLAVE_RSSI_CHANGED 注册了但 onReceive 无分支——死过滤项[待验证:历史遗留]。
onStop()(:640-673)把上述全部反注册(IllegalArgumentException 吞掉,:669-672);MIUI 另挂 ON_RESUME(:140-145 → onResume() :631-635 → Scanner.onResume 补扫)。
1.3 “handleMessage 分发”——本 fork 没有
全源集 grep handleMessage 零命中。老 AOSP 的 mHandler+MSG_xxx 已废弃,分发只剩两条:
- 广播 → worker 线程
onReceive的 if/else 链(:158-204); - 扫描定时 →
Scanner extends Handler用postDelayed(this::scanLoop, mScanIntervalMillis)自驱(:1152),无 msg.what。
1.4 扫描循环(Scanner 内部类 :946-1154)
- 门控
shouldScan()(:1073-1076):mIsWifiEnabled && mIsStartedState && !mIsScanningDisabled && mPowerManager.isInteractive()——页面 Started + WiFi 开 + 屏亮三 gate。 - AOSP U+ 的 WifiScanner 首扫快扫被 MIUI 整段注释(
MIUI DEL: WIFI_ScanControll:1085-1108),只剩 scanLoop:// scanLoop() :1119-1153 if (!isAppVisible()) { ... mIsScanLoopFlag = false; return; } // 进程不可见不扫(:1128-1137) removeCallbacksAndMessages(null); // 防多路重入(:1142) mWifiManager.startScan(); // 唯一扫描入口(:1144) notifyOnScanRequested(); // 主线程回调(:1148) postDelayed(this::scanLoop, mScanIntervalMillis); // 10s 周期重排(:1152) - 触发时机:
onStart()(:1023-1026)、onResume()(100ms 延迟补扫,:1029-1035)、onWifiStateChanged(true)(:1057-1067)。 - 库内无节流逻辑;
startScan()的限频在 WifiService 侧[待验证:车机 ROM 对 system uid 的豁免策略]。Settings 还有第二条直发入口:下拉刷新/WiFi 刚开时MiCarAvailableWifiListController.startScan()(:74-80)→MiCarWifiUtils.startScan()(MiCarWifiUtils.kt:157-159)。
2. WifiPickerTracker:列表页数据引擎
2.1 核心数据结构(:112-161)
| 字段 | 含义 |
|---|---|
mConnectedWifiEntry | 当前主连接条目(字段 :114-116 无 volatile;getter getConnectedWifiEntry :216-219) |
mActiveWifiEntries | 已连接/连接中集合 |
mWifiEntries | 未连接但在范围内的条目(UI 列表主体,含在范围内的已保存网) |
mStandardWifiConfigCache | 已保存配置缓存(ArrayMap<StandardWifiEntryKey, List |
mStandardWifiEntryCache | 可见标准条目总池(仅 worker 线程可碰 :135-136) |
mSuggestedConfigCache/EntryCache、mNetworkRequestConfigCache、Passpoint 三件套、mKnownNetworkEntryCache/mHotspotNetworkEntryCache | 上游带来的旁路 |
没有
mWifiEntryMap/mScannedWifiEntries这两个名字(那是别的版本);等价物 =mStandardWifiEntryCache+ScanResultUpdater。SSID 去重不靠 Map,靠 ScanResultKey 分组(§2.4)。
2.2 三个入口 handler
- handleWifiStateChangedAction(:356-363):WiFi 关 →
clearAllWifiEntries()(:294-302,清六个缓存)→updateWifiEntries()。这是唯一的全缓存清空点。 - handleScanResultsAvailableAction(:365-373):
EXTRA_RESULTS_UPDATED→conditionallyUpdateScanResults(scanSucceeded, scanSucceeded)→updateWifiEntries(WIFI_ENTRIES_CHANGED_REASON_SCAN_RESULTS)(reason=1,UI 可据此做动画)。 - handleOnStart(:304-354)(页面回前台):
updateWifiConfigurationsInternal()(:1352-1360;有 READ_WIFI_CREDENTIAL 走getPrivilegedConfiguredNetworks());- 对所有 entry
clearConnectionInfo(false)(清陈旧连接态不通知,防闪烁 :313-315); - 已连接(supplicant COMPLETED)则手动补
handleNetworkCapabilitiesChanged+handleLinkPropertiesChanged(:319-342); conditionallyUpdateScanResults(true, false)——MIUI 改动:timeoutScans 由 true 改 false(:346-349),回页面不用 15s 裁剪,改 5min 兜底窗,防列表瞬间清空;updateWifiEntries()。
2.3 conditionallyUpdateScanResults(:1231-1282)
if (getWifiState() == WIFI_STATE_DISABLED) { ...全空刷新各缓存... return; } // :1233-1250
final List<ScanResult> newScanResults = pollScans ? mWifiManager.getScanResults() : emptyList();
mScanResultUpdater.onScanResultsAvailable(newScanResults, timeoutScans);
List<ScanResult> scanResults = mScanResultUpdater.getScanResults();
if (scanResults.size() > 200) { /* MIUI: level 降序截断前200,防 binder 超限 :1257-1264 */ }
updateStandardWifiEntryScans(scanResults); // :1265 (主)
updateSuggestedWifiEntryScans / Passpoint / Osu / Known / Hotspot / NetworkRequest / Contextual ...2.4 条目怎么建/复用:updateStandardWifiEntryScans(:879-940)
// 1) 过滤空SSID,按 ScanResultKey(SSID+安全族) 分组 —— "同SSID去重"的真正位置
scanResults.stream().filter(s -> !isEmpty(s.SSID))
.collect(groupingBy(ScanResultKey::new)); // :885-887
// 2) 已有条目:命中 key 就 updateScanResultInfo() 复用,不 new :892-902
// 3) 剩余新 key:new StandardWifiEntry(...) 入缓存 :904-912
// 4) 跨用户共享配置补建 :916-934
// 5) removeIf(entry -> getLevel() == WIFI_LEVEL_UNREACHABLE) :938-939ScanResultKey 安全族合并(StandardWifiEntry.java:1619-1652)决定去重粒度:
- OPEN ↔ OWE 一族;PSK ↔ SAE 一族;EAP ↔ EAP_WPA3 一族;Passpoint R1/R2/R3 从 key 剔除。
- ⇒ 同名 SSID 的 PSK 和 SAE 两个 AP = 一条 entry;同名同安全类型的多 BSSID(如 2.4G+5G)= 一条 entry(BSSID 们躺在
mMatchingScanResults)。
2.5 updateWifiEntries:已连接 vs 可用拆分(:609-791)
activeWifiEntries= 各缓存中getConnectedState() != DISCONNECTED的(:620-621),再剔被 HotspotNetworkEntry 覆盖的(:622-633),按WIFI_PICKER_COMPARATOR排序(:638;comparator 在 WifiEntry.java:239-252:主网络 > 已连接 > Known > Hotspot > 可连接 > 订阅 > 已保存 > 建议 > 信号强 > 标题字典序)。- 装配
wifiEntries(未连接组)连续过滤(:671-705):已在 active 的跳过/可见建议跳过/Passpoint 供网跳过/与 Known 撞 key 跳过/跨用户共享跳过/已保存的收集进 savedEntryKeys 但仍留在 wifiEntries。 - 追加各组 disconnected 条目(:706-749)→
Collections.sort(:750)。 mConnectedWifiEntry = active 组第一个(:776-787;MIUI 改动去掉isPrimaryNetwork()校验)。notifyOnWifiEntriesChanged(reason)(:790 → :1553-1558):mMainHandler.post(() -> mListener.onWifiEntriesChanged(reason)),回调固定主线程。
车机”已保存列表”的真正来源:不是 SavedNetworkTracker,而是
MiWifiTrackerUtils.getSavedWifiEntries()(kt:101-117)对同一个mWifiEntries过滤reachable && isSaved(kt:127-131),再剔本机热点(kt:123-125)、要求level != UNREACHABLE(kt:126)。不在范围内的已保存网不显示(没有全量拉配置建表)。
2.6 回调主线程链
worker: updateWifiEntries() ─post─> main: onWifiEntriesChanged(reason)
→ MiWifiTrackerUtils$mTrackerListener:51-55(扇出给 mListeners)
→ MiCarBaseWifiEntryController#onWifiEntriesChanged → refreshUi
→ updateState(preference):96-120
├ ThreadPoolUtils.SINGLE_ASYNC 跑 fillWifiEntries(读 tracker 数据)
└ mHandler(主线程) sendMessage(UPDATE_MESSAGE_WHAT=0x101 :50) → 子类 updateState(pref, list) 重建单条 entry 的自刷新(不重排时):WifiEntry.setListener → notifyOnUpdated() → mCallbackHandler.post(WifiEntry.java:1016-1043,callbackHandler=mainHandler)。Controller 的 listener 注册/反注册在 onStartInternal/onStopInternal(MiCarBaseWifiEntryController.kt:176-184)。
3. WifiEntry:单网络”状态机 + 门面”
3.1 连接状态:3 态,不是 4 态
CONNECTED_STATE_DISCONNECTED = 0; // :138
CONNECTED_STATE_CONNECTING = 1; // :139
CONNECTED_STATE_CONNECTED = 2; // :140没有 DISCONNECTING(IntDef :129-136)。getConnectedState()(:324-349):
mNetworkCapabilities != null→ CONNECTED(L3 已通,:328-330);- 否则
mNetworkInfo.getDetailedState()∈ {SCANNING, CONNECTING, AUTHENTICATING, OBTAINING_IPADDR, VERIFYING_POOR_LINK, CAPTIVE_PORTAL_CHECK, CONNECTED} → CONNECTING(:333-346); - 否则 DISCONNECTED。
状态原料由 tracker 喂:
onNetworkCapabilitiesChanged(:1173-1207,networkId 不匹配则转手onNetworkLost)、onPrimaryWifiInfoChanged(NETWORK_STATE_CHANGED 广播,:1152-1166)、onNetworkLost(:1255-1263 →clearConnectionInfo:1268-1286)。
小白要点:DISCONNECTING 这个 UI 上存在的文案(“正在断开连接…“)来自 NetworkInfo.DetailedState 映射表,而不是 connectedState 枚举——别找 CONNECTED_STATE_DISCONNECTING,它不存在。
3.2 信号格数 mLevel:两套来源 + MIUI 私有换算
getLevel()(:382-387):连接时用 mWifiInfoLevel,否则 mScanResultLevel;域 0..4,WIFI_LEVEL_UNREACHABLE=-1(:143-145)。
- 连接侧:
updateWifiInfo()(:1209-1248)→WifiEntryUtilsStub.miuiCalculateSignalLevel(mContext, is24GHz, rssi, mWifiManager)(:1222-1226;2.4G 按 frequency 2401-2499 判,:1223)。 - 扫描侧:
StandardWifiEntry.updateTargetScanResultInfo()(:1013-1036)取最强 BSSID(getBestScanResultByLevel,Utils.java:108-112 =Collections.max按 level),仅未连接时更新(:1027-1030)。 - MIUI 换算(WifiEntryUtilsStub.java:43-70):非 MIUI 走
wifiManager.calculateSignalLevel;MIUI 走反射com.android.settingslib.wifi.WifiEntryUtils,阈值可云控(Settings.System "cloud_wifi_signal_level"),默认 2.4G {-91,-77,-65} / 非2.4G {-91,-79,-65}(:25-26)。注释:“miuiWifiManager 会把 max level 减 1,所以这里 +1”(:44)。
3.3 Security 体系
常量(:113-127):NONE=0, WEP=1, PSK=2, EAP=3, OWE=4, SAE=5, EAP_SUITE_B=6,MIUI 插入 WAPI_PSK=7, WAPI_CERT=8,EAP_WPA3_ENTERPRISE=9。getSecurityTypes() 由 updateSecurityTypes()(:1113-1174)决定优先级:WifiInfo 当前类型 > 无匹配 config 时扫描类型 > config 类型 > key 类型兜底;多类型坍缩到族里最低档(Utils.java:241-262:Open/PSK/EAP)。
3.4 shouldEditBeforeConnect(:966-968 基类 false;StandardWifiEntry:866-886)
NetworkSelectionStatus status = wifiConfig.getNetworkSelectionStatus();
if (status.getNetworkSelectionStatus() != NETWORK_SELECTION_ENABLED || !status.hasEverConnected()) {
if (counter(DISABLED_AUTHENTICATION_FAILURE) > 0
|| counter(DISABLED_BY_WRONG_PASSWORD) > 0
|| counter(DISABLED_AUTHENTICATION_NO_CREDENTIALS) > 0) return true;
}语义:已保存但因认证失败/错密码被框架禁用 → 点击应先弹编辑框。车机消费点 MiCarBaseWifiEntryController.kt:218-223(MANNPROB-3405 修复:命中直接 showWifiPasswordDialog(wifiEntry, true))。
3.5 connect():基类 no-op + StandardWifiEntry 四分支(:508-611)
mShouldAutoOpenCaptivePortal = true; // :514 手动连接→门户自动弹
if (isSaved() || isSuggestion()) { // ① 已保存/建议:静默直连
if (SIM 凭据缺 SIM) { callback.onConnectResult(FAILURE_SIM_ABSENT); return; } // :517-525
mWifiManager.connect(mTargetWifiConfig.networkId, new ConnectActionListener(this)); // :534
return;
}
if (含 OWE) { ...构造 oweConfig connect; OWE过渡网再 save(openConfig)... } // ② :549-580
if (含 OPEN) { ...构造 openConfig connect... } // ③ :582-603
callback.onConnectResult(CONNECT_STATUS_FAILURE_NO_CONFIG); // ④ :605-610 → 上层弹密码框关键细节:
ConnectActionListener.onSuccess只置mCalledConnect=true——真正 SUCCESS 要等 L3 连上(updateWifiInfo:1228-1238);受理失败回CONNECT_STATUS_FAILURE_UNKNOWN(:1496-1503)。- 车机密码框路径不走 WifiEntry.connect:App 层
MiCarWifiUtils.connect()(:51-69)自己组WifiConfiguration直连WifiManager.connect(config, ActionListener)。 canConnect()(:443-485):UNREACHABLE 且非”10 秒内刚被用户断开”(USER_RECENTLY_DISCONNECTED_TIMEOUT_MS=10_000:117)则 false。forget()(:641-649):mWifiManager.forget(networkId, ...);disconnect()(:619-633):mWifiManager.disconnect()+ 记mLastUserDisconnectTimestampMs。
4. Entry 子类实况:车机只实例化 StandardWifiEntry
| 类 | 场景 | 车机实际(settingsPage 全量 grep) |
|---|---|---|
| StandardWifiEntry | 普通 open/saved/suggestion 网 | 唯一主力,16 处 import WifiEntry、4 处 import WifiPickerTracker,再无其他 |
| KnownNetworkEntry | U+ 跨设备已知网(SharedConnectivity) | 依赖 ROM 服务[待验证],现网基本不出现 |
| HotspotNetworkEntry | U+ 附近设备热点 | 同上 |
| PasspointWifiEntry / OsuWifiEntry | Hotspot2.0 订阅/注册 | 需 provision 过 Passpoint,车机罕见[待验证] |
| MergedCarrierEntry | 运营商合并网(new 在 updateMergedCarrierEntry :816,由 handleDefaultSubscriptionChanged :513-515 ← handleOnStart :318 / 订阅变更广播触发) | Settings 侧无 getMergedCarrierEntry 外部消费,但车机有 SIM(默认数据订阅存在)即会实例化——勿按”永不实例化”排除 |
| NetworkRequestEntry | specifier 请求式连接 | 仅第三方请求场景(见 30 篇 §6) |
| SavedNetworkTracker(第二 tracker) | AOSP 已保存页专用 | 车机不用(零引用) |
5. ScanResultUpdater:84 行的”合并窗”
- 结构:
Map<Pair<SSID,BSSID>, ScanResult>(:38,mLock 线程安全)。 onScanResultsAvailable(new, timeoutScans)(:59-73):同 SSID+BSSID 只留 timestamp 更新的;timeoutScans=true裁mMaxScanAgeMillis(车机 15s,MiWifiTrackerUtils.kt:29),false 用硬编码 5 分钟(:36,:69-71)——“连续扫描失败时别把列表清空”。- 车机参数:10 秒一扫、15 秒过期——连续错过 1 轮扫描才开始掉条目;扫描失败/页面重启(MIUI timeoutScans=false)放宽到 5 分钟。
6. WifiTrackerInjector:注入了什么
f3dif(:41-189):Context、Clock(扫描老化/断开计时)、ConnectivityManager、UserManager/DPM、wifi-state 缓存(:118-129)、verbose 三态缓存(:136-169)。执行器不在此注入(Handler 构造直传;WorkerExecutor 只是 mWorkerHandler::post 包装 :321-337)。车机定制仅:构造器 public 化(MIUI MOD :63-65)+ 抓 log 时抑制 verbose summary(Settings.Global catch_wifi_log_in_progress,:151-162)。
7. 车机接入:每页一份 tracker,详情页共享 entry 引用
MiCarWifiListFragment#onCreate:74 → MiWifiTrackerUtils(lifecycle)
├ HandlerThread "MiWifiTrackerTools{hash}" BACKGROUND(kt:40-43)
├ ELAPSED_REALTIME_CLOCK(kt:34-38)
└ createWifiPickerTracker(...,15_000,10_000,listener)(kt:65-74)
└ MiCarWifiCollinear.kt:17-38(f3dif flavor 接缝)→ new WifiPickerTracker(...)- 每页一份(列表页/引导页各建各的,MiCarWifiProvisionFragment.kt:46),Controller 经
InstanceGetter共享(MiCarBaseWifiEntryController.kt:91-92)。 - 销毁:
onDestroy→ removeListener +destroy()(quit 线程,kt:134-138);tracker 无 onDestroy 反注册——onStop 已拆全部广播。 - 详情页不建 tracker:直接持静态
MiCarWifiUtils.detailWifiEntry的同一 entry 对象(MiCarWifiEntryDetailFragment.kt:47-51),其状态更新依赖列表页 tracker 仍活着。排查”详情页状态不刷新”先查列表页 tracker 生命周期(onStop 即停)。
8. 排查速查表
| 现象 | 先看 |
|---|---|
| 列表不刷新/不扫描 | shouldScan() 四条件(:1073-1076,灭屏=不扫);scanLoop 的 isAppVisible 日志(:1122-1132);10s 周期 |
| 条目闪现/闪没 | ScanResultUpdater 15s/5min 双窗 + App 侧 500ms 空防护 + handleOnStart 清建顺序(:304-354) |
| 同 SSID 两条/少一条 | ScanResultKey 安全族合并(StandardWifiEntry:1619-1652)+ updateWifiEntries 5 层过滤(:671-705) |
| 信号格数异常 | miuiCalculateSignalLevel 云控阈值(WifiEntryUtilsStub:24-26/:43-70);连接态用 WifiInfo rssi、未连接用最强 BSSID |
| 连接无反馈 | NO_CONFIG→密码框(MiCarBaseWifiEntryController:324-329);SUCCESS 等 L3(WifiEntry:1228-1238);错密码→shouldEditBeforeConnect |
| ”已保存”分组错 | isSaved = 有 config 且非 suggestion 非 ephemeral(StandardWifiEntry:415-418);isNeedAddInList(kt:122-134) |
[待验证汇总]:①ROM 是否提供 SharedConnectivityManager;②startScan 对 system uid 的实际限频;③SLAVE_RSSI_CHANGED 死过滤项来历;④A17 上 isAtLeastB() 是否 true;⑤现网有无 Passpoint/Specifier 场景。