21 · WifiTrackerLib 架构:双线程、条目缓存与状态机

核心文件:base/WifiTrackerLib/src/f3dif/java/com/android/wifitrackerlib/ 下的 BaseWifiTracker.java(1200 行)、WifiPickerTracker.java(1634 行)、WifiEntry.java(1741 行)、StandardWifiEntry.javaScanResultUpdater.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 values
  • mMainHandler/mWorkerHandler 均为构造注入(:466-477,字段 :218-219)。
  • 库内没有 mWorkerThread——车机侧由 Settings 建:MiWifiTrackerUtils.kt:40-43HandlerThread(TAG+{hash}, THREAD_PRIORITY_BACKGROUND),onDestroyquit()(: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)
listeneraddWifiStateChangedListener(WifiSCListener :352-377)SDK>35 且开关打开(Injector:186-188)同上;注册后立刻 post 一次(:566-570)
listenerverbose 状态 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)
CMregisterNetworkCallback(TRANSPORT_WIFI+NOT_VPN, FLAG_INCLUDE_LOCATION_INFO, worker)(:617-618)总是onLinkPropertiesChanged/onCapabilitiesChanged/onLost(:238-282,带主从/MLO 过滤)
CMregisterDefaultNetworkCallback(worker)(:619-620)总是mDefaultNetworkCallback(:284-298)
CDMConnectivityDiagnostics(:621-622)总是handleConnectivityReportAvailable(弱引用 :301-319)
SCMSharedConnectivity(: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 已废弃,分发只剩两条:

  1. 广播 → worker 线程 onReceive 的 if/else 链(:158-204);
  2. 扫描定时 → Scanner extends HandlerpostDelayed(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/EntryCachemNetworkRequestConfigCache、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_UPDATEDconditionallyUpdateScanResults(scanSucceeded, scanSucceeded)updateWifiEntries(WIFI_ENTRIES_CHANGED_REASON_SCAN_RESULTS)(reason=1,UI 可据此做动画)。
  • handleOnStart(:304-354)(页面回前台):
    1. updateWifiConfigurationsInternal()(:1352-1360;有 READ_WIFI_CREDENTIAL 走 getPrivilegedConfiguredNetworks());
    2. 对所有 entry clearConnectionInfo(false)(清陈旧连接态不通知,防闪烁 :313-315);
    3. 已连接(supplicant COMPLETED)则手动补 handleNetworkCapabilitiesChanged+handleLinkPropertiesChanged(:319-342);
    4. conditionallyUpdateScanResults(true, false)——MIUI 改动:timeoutScans 由 true 改 false(:346-349),回页面不用 15s 裁剪,改 5min 兜底窗,防列表瞬间清空;
    5. 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-939

ScanResultKey 安全族合并(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)

  1. activeWifiEntries = 各缓存中 getConnectedState() != DISCONNECTED 的(:620-621),再剔被 HotspotNetworkEntry 覆盖的(:622-633),按 WIFI_PICKER_COMPARATOR 排序(:638;comparator 在 WifiEntry.java:239-252:主网络 > 已连接 > Known > Hotspot > 可连接 > 订阅 > 已保存 > 建议 > 信号强 > 标题字典序)。
  2. 装配 wifiEntries(未连接组)连续过滤(:671-705):已在 active 的跳过/可见建议跳过/Passpoint 供网跳过/与 Known 撞 key 跳过/跨用户共享跳过/已保存的收集进 savedEntryKeys 但仍留在 wifiEntries
  3. 追加各组 disconnected 条目(:706-749)→ Collections.sort(:750)。
  4. mConnectedWifiEntry = active 组第一个(:776-787;MIUI 改动去掉 isPrimaryNetwork() 校验)。
  5. 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.setListenernotifyOnUpdated()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 != nullCONNECTED(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=9getSecurityTypes()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,再无其他
KnownNetworkEntryU+ 跨设备已知网(SharedConnectivity)依赖 ROM 服务[待验证],现网基本不出现
HotspotNetworkEntryU+ 附近设备热点同上
PasspointWifiEntry / OsuWifiEntryHotspot2.0 订阅/注册需 provision 过 Passpoint,车机罕见[待验证]
MergedCarrierEntry运营商合并网(new 在 updateMergedCarrierEntry :816,由 handleDefaultSubscriptionChanged :513-515 ← handleOnStart :318 / 订阅变更广播触发)Settings 侧无 getMergedCarrierEntry 外部消费,但车机有 SIM(默认数据订阅存在)即会实例化——勿按”永不实例化”排除
NetworkRequestEntryspecifier 请求式连接仅第三方请求场景(见 30 篇 §6)
SavedNetworkTracker(第二 tracker)AOSP 已保存页专用车机不用(零引用)

5. ScanResultUpdater:84 行的”合并窗”

  • 结构:Map<Pair<SSID,BSSID>, ScanResult>(:38,mLock 线程安全)。
  • onScanResultsAvailable(new, timeoutScans)(:59-73):同 SSID+BSSID 只留 timestamp 更新的;timeoutScans=truemMaxScanAgeMillis(车机 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 场景。