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-74createWifiPickerTracker(...) 创建 tracker(见 §3)

两个关键常量:

常量位置语义
DEFAULT_MAX_SCAN_AGE_MILLIS15_000L:29扫描新鲜窗:超过 15s 的扫描结果视为过期
DEFAULT_SCAN_INTERVAL_MILLIS10_000LMiCarWifiUtils.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 处):

实现者onWifiEntriesChangedonWifiStateChanged
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 :74onDestroy :134-138(removeListener + destroy)
MiCarWifiProvisionFragment(:23,开机引导页)onCreate :46onDestroy :106-109(仅 destroy)

详情页 MiCarWifiEntryDetailFragment(:29)不碰 tracker——它要的那个 entry 走全局单例 MiCarWifiUtils.detailWifiEntry(MiCarWifiUtils.kt:31)传递(赋值点 MiCarSavedWifiListController.kt:57)。

5. 查询 API:三个门面方法 + 过滤三件套

方法行号语义消费方
getConnectedWifiWifiEntry():87-89直取 tracker.connectedWifiEntrySaved 列表 :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。