23 · App 侧封装:MiWifiTrackerUtils(tracker 装配器与过滤门面)
核心文件:
settingsPage/micarConnectionSettings/src/main/java/com/android/car/settings/miauto/wifi/MiWifiTrackerUtils.kt(全文 162 行,main 源集,全 flavor 共用) 代码基线:fuben_micarsettings @ dev,f3 flavor;所有 file:line 均在该基线逐一复核(与 fuben10 @ rls-xcd-u-ee-2603 diff 一致) 关联:21-WifiTrackerLib架构(本类包装的库本体)、22-flavor 差异、30-页面与导航(宿主 Fragment 装配)、32-扫描与列表刷新
0. 一句话定位 + 它不是什么
定位:App 侧对 vendored WifiPickerTracker 的装配器 + 门面(Facade)——负责建后台线程、按 flavor 创建 tracker、把库回调翻译成自己的 Listener 接口、对列表查询做车设特有的过滤(本机热点排除 + 15s 新鲜窗)。
它不是什么:
- 不是单例——每个宿主页面各 new 一份(见 §4,这是性能类 bug 的结构根源);
- 不做扫描/连接本身——那是库里
WifiPickerTracker+系统WifiService的事; - 类声明
implements LifecycleObserver(:26)是死代码——全类没有任何@OnLifecycleEvent方法,也从不把自己注册进任何 lifecycle。生命周期联动完全是库里自己做的(见 §3)。
1. 构造即全副武装:属性初始化器里做完 4 件事
MiWifiTrackerUtils(lifecycle) 一 new 出来就完成全部装配,没有惰性、没有 init 方法:
| # | 成员 | 行号 | 干什么 |
|---|---|---|---|
| ① | ELAPSED_REALTIME_CLOCK | :34-38 | 扫描时效判定时钟,用 SystemClock.elapsedRealtime()(休眠不计时,比 wall clock 准) |
| ② | mWorkerThread | :40-43 | 新建 HandlerThread 立即 start,后台优先级 THREAD_PRIORITY_BACKGROUND(:42);线程名 = MiWifiTrackerTools{实例hash}(:41) |
| ③ | mTrackerListener | :44-64 | 匿名 WifiPickerTracker.WifiPickerTrackerCallback,把库回调扇出给 mListeners(见 §2) |
| ④ | mWifiTracker | :65-74 | 调 createWifiPickerTracker(...) 创建 tracker(见 §3) |
两个关键常量:
| 常量 | 值 | 位置 | 语义 |
|---|---|---|---|
DEFAULT_MAX_SCAN_AGE_MILLIS | 15_000L | :29 | 扫描新鲜窗:超过 15s 的扫描结果视为过期 |
DEFAULT_SCAN_INTERVAL_MILLIS | 10_000L | MiCarWifiUtils.kt:19(import 于 :16) | tracker 周期扫描间隔 |
线程名带实例 hash 的副作用:日志里三次开页出现三个不同的 MiWifiTrackerTools{hash} 线程,就是”每次进页都新建”的铁证——MIICOSBUG-2759 定案时正是靠这个指纹坐实无复用。
2. 回调翻译层:库无参回调 → App 带参 Listener
库的 WifiPickerTrackerCallback 全是无参回调,本类翻译成带参的自有 Listener 接口(:140-157),扇出给 mListeners(:39):
// :45-49 库回调无参 → 主动查当前状态再下发
override fun onWifiStateChanged() {
mListeners.forEach { it.onWifiStateChanged(MiCarWifiUtils.getWifiState()) } // getWifiState: MiCarWifiUtils.kt:175
}
// :51-55 列表变化原样扇出
override fun onWifiEntriesChanged() { mListeners.forEach { it.onWifiEntriesChanged() } }
// :57-63 onNumSavedNetworksChanged / onNumSavedSubscriptionsChanged 空实现——车设不用自有 Listener 只有两个方法:onWifiEntriesChanged()(:145)、onWifiStateChanged(state)(:156,state 取值 WIFI_STATE_DISABLED/ENABLED/DISABLING/ENABLING/UNKNOWN)。
谁实现了 Listener(全仓穷举,3 处):
| 实现者 | onWifiEntriesChanged | onWifiStateChanged |
|---|---|---|
MiCarWifiListFragment(:28) | 空实现(:151) | 开关态切”WLAN 关闭提示条”可见性(:153-160) |
WifiSwitchController(:32) | 空实现(:84) | 开关翻面 + loading 态(:74-83) |
MiCarBaseWifiEntryController(:44,三个列表 Controller 的基类) | 子类各自实现 → refreshUi()(见 §5) | (基类未用) |
注册时机:Fragment 在 onCreate 紧随 new 之后 addListener(this)(MiCarWifiListFragment.kt:75);Controller 在 onStartInternal 注册 / onStopInternal 反注册(MiCarBaseWifiEntryController.kt:178/183;WifiSwitchController.kt:59/63)。
⚠️ 反直觉点:宿主 Fragment 自己的 onWifiEntriesChanged 是空的——真正响应列表变化的是 Controller 层。Fragment 只用 state 回调管”WLAN 已关闭”提示条。
3. tracker 创建:flavor 接缝 + 生命周期绑定在库里
createWifiPickerTracker 不在 main 源集,是顶层函数,每个 flavor 一份(MiCarWifiCollinear.kt):f3dif :17 / xcddif :17 / dcddif :20,签名一致,本体就是 new WifiPickerTracker(...)(f3dif :27-38)。作用=把平台差异(A14/A17 的库构造签名)隔离在 flavor 源集,main 只调抽象(详见 22 篇)。
传入的 8 个参数(:65-74)里两个 Handler 决定线程模型:mainHandler(Handler(Looper.getMainLooper()),:68)负责回调回主线程;workerHandler(mWorkerThread.threadHandler,:69)承载全部重活。
生命周期联动不在本类,在库里:f3dif BaseWifiTracker 持有 WifiTrackerLifecycleObserver(:132-152,ON_START :133 / ON_RESUME :140 / ON_STOP :147),构造尾部把它 post 到主线程注册进传入的 lifecycle(:525-526)。于是:
- 页面 START →
onStart()(:552)→mWorkerHandler.post { handleOnStart() }(:557-560)——串行 binder 链从这里发起(2759 的门控点); - 页面 STOP →
onStop()(:641)→ 反注册 receiver/回调(:655-659 等)。
flavor 差异提示:xcddif(A14)额外有 ON_DESTROY 观察(:410)→ onDestroy()(:545)做反注册;f3dif 没有 onDestroy,全部清理压在 onStop 里做。两套都配合 App 侧 Fragment onDestroy 调 destroy()(:136-138,仅 quit worker 线程)。
4. 实例分发:InstanceGetter 模式(每页一份,Controller 不自建)
tracker 实例的”户主”是 Fragment,Controller 通过接口向户主借:
// MiCarBaseWifiEntryController.kt:91-92 / WifiSwitchController.kt:51
mWifiTrackerTools = (fragmentController.hostFragment as MiWifiTrackerUtils.InstanceGetter).getInstance()全仓只有两个户主(实现 InstanceGetter,:159-161):
| 户主 | 创建点 | 销毁点 |
|---|---|---|
MiCarWifiListFragment(:28) | onCreate :74 | onDestroy :134-138(removeListener + destroy) |
MiCarWifiProvisionFragment(:23,开机引导页) | onCreate :46 | onDestroy :106-109(仅 destroy) |
详情页 MiCarWifiEntryDetailFragment(:29)不碰 tracker——它要的那个 entry 走全局单例 MiCarWifiUtils.detailWifiEntry(MiCarWifiUtils.kt:31)传递(赋值点 MiCarSavedWifiListController.kt:57)。
5. 查询 API:三个门面方法 + 过滤三件套
| 方法 | 行号 | 语义 | 消费方 |
|---|---|---|---|
getConnectedWifiWifiEntry() | :87-89 | 直取 tracker.connectedWifiEntry | Saved 列表 :78;Available 列表 :64(算 mStartIndex);Provision :49 |
getAllWifiEntries() | :94-96 | = getWifiEntries(false) 可用列表 | Available 列表 :56;Provision :54 |
getSavedWifiEntries() | :101-103 | = getWifiEntries(true) 已保存列表 | Saved 列表 :84 |
getWifiEntries(isOnlyNeedSaved)(:105-117):先 HotspotUtils.refreshHotspotInfo()(:106,HotspotUtils.kt:112 刷新本机热点 BSSID 集合)→ WiFi 关闭直接返回空(:108)→ 逐 entry 过 isNeedAddInList(:110)。
isNeedAddInList(:122-134)过滤三件套:
if (HotspotUtils.isHotspot(wifiEntry)) return false // :123-125 ①本机热点不进列表(按 BSSID 匹配,HotspotUtils.kt:129)
val reachable = wifiEntry.level != WIFI_LEVEL_UNREACHABLE // :126 ②15s 新鲜窗:过期扫描 level=-1
return if (isOnlyNeedSaved) reachable && wifiEntry.isSaved// :127-128 ③已保存组还要 isSaved
else reachable // :129-131★ ②是 MIICOSBUG-2759 / MIICOSBUG-2548 共同的承重逻辑:扫描缓存过期(>15s)时 level=UNREACHABLE,可用列表和已保存列表同时被清空——哪怕该网络此刻正处于已连接状态。
6. 端到端链路:从”页面可见”到”列表上屏”
Fragment onCreate:74 new MiWifiTrackerUtils(lifecycle)
├─ 建后台线程(:40-43)+ 建 tracker(:65-74 → 库构造里 addObserver)
Fragment onStart
└─[lifecycle 联动] 库 BaseWifiTracker.onStart(f3dif :552)
└─ worker 线程 handleOnStart()——串行 binder 链(已保存配置/扫描结果/当前网络…)
└─ updateWifiEntries 完成 → mainHandler 回调 onWifiEntriesChanged
└─ 本类 :51-55 扇出 → 三个列表 Controller.onWifiEntriesChanged
└─ refreshUi()(PreferenceController.java:359 → :370 updateState)
└─ MiCarBaseWifiEntryController.updateState(:96)
├─ WiFi 关 → isVisible=false + removeAll(:103-107)
└─ 开 → 单线程池执行(:108-119):fillWifiEntries(:111)
├─ Available 组(:54-66):getAllWifiEntries 逐条过滤,已保存的跳过并计入 mStartIndex
├─ Saved 组(:77-91):先摆已连接卡片(:78-82),再追加已保存列表(:84-90)
└─ Provision 组(:47-56):已连接 + 全量
→ 回主线程 updateState(preference, list)(:113-117)上屏7. 与 MIICOSBUG-2759 的映射(为什么这个类是风暴起点)
| 设计 | 行号 | 后果 | 2759 修复方案落点 |
|---|---|---|---|
| 每开页 new、无复用 | :24 + 户主 :74/:46 | 每次进页重建线程+tracker+全量注册 | 方案③:改进程级单例 |
| worker 线程后台优先级 | :42 | 高负载下被调度压制,binder 墙钟膨胀 | (配套方案③改优先级或复用) |
| 15s 新鲜窗常量 | :29 | 过期即 UNREACHABLE | 方案④的判定源 |
isNeedAddInList 要求 reachable | :126-131 | 过期时可用+已保存双列表同空 | 方案④:isSaved 已连接豁免 reachable |
8. 速查:排障时看这个类的什么
- 怀疑”每次进页都重建”→ 日志 grep 线程名
MiWifiTrackerTools{的 hash 是否每次都变; - 怀疑”双列表被过滤空”→ 看
isNeedAddInList: wifiEntry=..., res=false(:132)与getWifiEntries: ..., size=0(:115)成对出现; - 怀疑”tracker 没起来”→ 库侧
onStart/handleOnStart end日志(f3dif :552 起 / xcddif :442 起); - 回调没扇出 → 确认 Controller 是否过了
onStartInternal(注册点才在 :178/:59),Fragment 的 addListener 在 :75。