个人热点全流程
模块根:
settingsPage/micarConnectionSettings/src/main/java/com/android/car/settings/miauto/hotspot/
0. 关键结论(高频踩坑)
- 存在两套并行实现!改 A 不影响 B,排查前必先确认入口:
- A 套:
MiCarHotspot*(大写 S)+WifiTetheringHandler+hotspot/HotSpotViewModel+TetherModifyDialogHelper - B 套:
Hotspot*+HotspotUtils+viewmodel/HotspotViewModel+HotspotConfigChangeDialog
- A 套:
- 两套”配置变更后重启”机制:
WifiTetheringHandler.mRestartBooked(:186-193)vsHotspotUtils.mIsNeedReopen(:46-55,275) - 真正 startTethering 实质只有一处:
mMiCarWifiManager.startMicarSoftAp(MicarWifiManager.TETHERED_SOFTAP),被 A 套WifiTetheringHandler.startTethering():204-206和 B 套HotspotUtils.openHotspot():211-213各封装。不用WifiManager.startTethering/TetheringManager。 - 客户端列表靠
MicarSoftApCallback(非标准TetheringEventCallback),弱引用防泄漏。 HotspotClientInfo必须基于 MAC 重写 equals/hashCode,否则 AsyncListDiffer 出现”Unknown 丢失/REDMI 重复”(viewmodel/HotspotClientInfo.kt:22-35)。
1. 架构总览
flowchart TD Entry[入口 HOTSPOT_SETTINGS Intent / 路由] --> Which{进入形态} Which -->|独立弹窗/老式列表| A1[A套 MiCarHotspotSettingsActivity] Which -->|全屏页/投屏自动打开| B1[B套 HotspotSettingActivity IHotspotRouter] A1 --> A2[MiCarHotSpotFragment 双栏XML] A2 --> A4["Left: TetherSwitch+HotSpotSharePreference(QR)<br/>Right: HotSpotSettings+TetherNetShare+TetherClients"] B1 --> B2[HotspotMainFragment 主页] B2 -->|设置|R1[showSettingsDialog→HotspotConfigChangeDialog] B2 -->|已连接|R2[HotspotConnectedFragment RecyclerView] B2 -->|小米互联|R3[HotspotForMiConnectionFragment] B2 -->|CarPlay|R4[HotspotForCarplayFragment LocalOnlyHotspot]
A 套走应用内路由
CarHotSpotSettings.HOT_SPOT_URI_PATH(@RouterProvideronMiCarHotSpotFragment);B 套走android.settings.HOTSPOT_SETTINGSIntent /carsettings://hotspot,manifest singleTask taskAffinity:HotSpotSettings。
2. 开关与状态机
四态(WifiManager.WIFI_AP_STATE_*):DISABLED(1)/ENABLING(2)/ENABLED(3)/DISABLING(4)。
| 状态 | A 套(WifiTetheringHandler.handleWifiApStateChanged:161-202) | B 套(HotspotSettingActivity.handleSwitch:165-196) |
|---|---|---|
| ENABLING | disablePreference(开关 loading+不可点) | isLoading=true, isEnabled=false |
| ENABLED | enablePreference + onWifiTetheringAvailable→VM postValue(true) | 开关勾选、occupancy 隐藏、container 显示 |
| DISABLING | disablePreference + onWifiTetheringUnavailable | UI 先行:取消勾选、container 隐藏 |
| DISABLED | onWifiTetheringUnavailable + enablePreference;mRestartBooked→再 startTethering | 开关不勾、dismiss 配置弹窗 |
开关调用点:
- A 套:
TetherSwitchController.onCreateInternal:30-33→HotSpotViewModel.setTetheredSoftApState(true):145-153→WifiTetheringHandler.startTethering():204-206→startMicarSoftAp(TETHERED_SOFTAP) - B 套:
HotspotSettingActivity.initView:93-103setOnCheckedChangeListener →HotspotUtils.openHotspot():211-213→ 同 startMicarSoftAp - 状态回调
MicarSoftApCallback.onTetheredSoftApStateChanged(state, errorCode),注册点:A 套WifiTetheringHandler.onStartInternal:105-113/HotSpotViewModel.init:82-87;B 套HotspotUtils.init:58-68/viewmodel/HotspotViewModel.init:30-37 - 投屏自动打开:B 套
onResume→handleAutoOpen():301-312读auto_enable=1;A 套TetherSwitchController.onResumeInternal:45-60
stateDiagram-v2 [*] --> DISABLED DISABLED --> ENABLING: openHotspot/startTethering ENABLING --> ENABLED: callback(ENABLED) ENABLED --> DISABLING: closeHotspot/stopTethering (UI先行) DISABLING --> DISABLED: callback(DISABLED) DISABLED --> DISABLED: mRestartBooked/mIsNeedReopen→再 startTethering
3. 配置管理(SSID/密码/频段/安全)
持久化
写 SoftApConfiguration→WifiManager.setSoftApConfiguration(framework 持久化):
- A 套:
HotSpotViewModel.modifyHotSpot:121-134→WifiTetheringHandler.configSoftAp:212-217(先 stopTethering 再 setSoftApConfiguration,置mRestartBooked=true) - B 套:
HotspotUtils.setSoftApConfiguration:265-278(置mIsNeedReopen=true→closeHotspot→setSoftApConfiguration) - 频段/安全/隐藏强制覆写(
WifiTetheringHandler.configSoftApBand:225-249,仅 ACTION_RESTART_WIFI_TETHERING 时 restartTethering 调):双频 BAND_5GHZ+BAND_2GHZ;WPA3 SAE/SAE_TRANSITION→降级 WPA2_PSK;setHiddenSsid(true);setAutoShutdownEnabled(false);setBridgedModeOpportunisticShutdownEnabled(false)。用户不可控频段/隐藏性/自动关机。 - 安全继承:修改时 Builder(oldConfig) 保留原 securityType;OPEN 则 passphrase 传 null(HotspotUtils.kt:268-273)
修改入口(两套对话框)
| 对话框 | 使用方 | 校验 |
|---|---|---|
TetherModifyDialogHelper(A) | MiCarHotSpotFragment.buildConfigDialog:96 / HotSpotSettingsController:64 | 名称非空+密码≥micar_ui_hotspot_password_length_min;名称≤32 字节 |
HotspotConfigChangeDialog(B) | HotspotSettingActivity.showSettingsDialog:222 | 名称非空+密码≥8 位+ASCII 可编码;截断 32 字节;有 AA 历史设备则二次确认 |
sequenceDiagram participant User participant VM as HotSpotViewModel participant Handler as WifiTetheringHandler participant Wifi as MicarWifiManager User->>VM: modifyHotSpot(name,pwd) VM->>Handler: configSoftAp(newConfig) Handler->>Wifi: stopMicarSoftAp() Handler->>Wifi: setSoftApConfiguration(newConfig) Handler->>Handler: mRestartBooked=true Wifi-->>Handler: callback(DISABLED) Handler->>Handler: mRestartBooked→startTethering() Handler->>Wifi: startMicarSoftAp(TETHERED_SOFTAP) Wifi-->>VM: callback(ENABLED)→postValue(true)
B 套差异:
HotspotUtils.setSoftApConfiguration不经 Handler,靠自身mReopenCallback(HotspotUtils.kt:47-56)在 DISABLED 时 openHotspot。
网络共享开关(独立)
TetherNetShareController/HotspotUtils.openNetShare 写 Settings.Global EasyTetheredEnabled(TetherNetShareController.kt:23-52, HotspotUtils.kt:225-231)。
4. 分享机制(QR)
HotspotQrCodeUtils.kt 生成标准 WIFI 协议串(getQrcodeText:117-135):
WIFI:T:<security>;P:<password>;S:<ssid>;H:<isHidden>;
- security 映射(getSecurityTypeIndex:81-92):WPA2_PSK→WPA,WPA3_SAE/SAE_TRANSITION→SAE,其余→空(open)
- ssid/password 经 addBackSlash 转义
;与\ - ZXing
QRCodeWriter,ErrorCorrectionLevel.H,margin=1,色取自hotspot_qrcode_pixel_color/hotspot_qr_bg
两渲染入口:A 套 HotSpotSharePreference.refreshQrCode:37-47(用 common.QRCodeUtils,注意不是 HotspotQrCodeUtils);B 套 HotspotMainFragment.refreshQRCode:104-123(用 HotspotQrCodeUtils,lifecycleScope 协程,ViewOutlineProvider 圆角)。HotSpotShareController 仅管可见性(热点开才显示),不主动 refresh。
5. 已连接客户端
数据源 MicarSoftApCallback.onTetheredSoftapConnectedClientsChanged(Map<String,MicarSoftApClientInfo>)(非标准 TetheringEventCallback)。
A 套(TetherClientsPreferenceController):观察 VM tetherClients;updateDevices 先 removeAll 再 addPreference;图标按 getSoftApClientType(PHONE/PAD/其他);名称优先 hostname 乱码 fallback macAddress(HotspotUtils.getSoftApName:152-159);阈值 ≥6 “连接设备过多” ≥10 “已达上限”;点击无回调仅展示。
B 套(HotspotConnectedFragment+HotspotListAdapter):RecyclerView 观察 connectedDevices;空态显示 mEmptyOccupancy;diff 正确性强依赖 HotspotClientInfo 基于 MAC 的 equals/hashCode。
sequenceDiagram participant FW as MicarWifiManager participant VM as viewmodel/HotspotViewModel participant Frag as HotspotConnectedFragment participant Adapter as HotspotListAdapter FW->>VM: onTetheredSoftapConnectedClientsChanged(map) VM->>VM: 包装 List~HotspotClientInfo~ postValue VM-->>Frag: LiveData observe alt 空 Frag->>Frag: 显示 mEmptyOccupancy else 非空 Frag->>Adapter: submitList Adapter->>Adapter: DiffUtil equals/hashCode(基于MAC) end
6. 多形态分发(CarPlay/MiConnection/普通)
接口 IHotspotRouter(view/IHotspotRouter.kt:6-26)4 方法:showSettingsDialog/showConnectedDevicePage/showMiConnectionPage/showCarplayPage。实现 HotspotSettingActivity,消费者 HotspotMainFragment.onViewCreated:45。
| Fragment | 数据源 | 场景 |
|---|---|---|
| HotspotMainFragment(普通) | HotspotViewModel + getSoftApConfig | 主页:QR+名称+已连接数 |
| HotspotForCarplayFragment | getLocalOnlyHotspotConfiguration().ssid(:37-41) | CarPlay 引导,展示 LocalOnlyHotspot SSID |
| HotspotForMiConnectionFragment | DeviceUtil.getDeviceName(:22-24) | 小米互联引导,展示车机名 |
| HotspotConnectedFragment | connectedDevices | 已连接客户端列表 |
本质区别:CarPlay 取 LocalOnlyHotspotConfiguration(设备互联热点 LOHS,无外网),MiConnection 取 DeviceUtil.getDeviceName(车机名)。普通态展示 TetheredSoftAp(公共热点)。4 入口均用户点击触发,showFragment:263-282 固定 replace+addToBackStack。
LocalOnlyHotspot vs TetheredSoftAp:
MicarWifiManager.TETHERED_SOFTAP=公共热点;LOHS=设备互联热点无外网,HotspotUtils.getLocalOnlyHotspotConfiguration:291-293读取,仅 CarplayFragment 用。两者配置独立、客户端回调独立(LOHS 回调在两 VM 中均空实现)。
7. WifiTetherStatusController(连接主页热点入口状态)
controller/WifiTetherStatusController.java,挂连接主页入口项,仅显示”已开启/已关闭”文案。
onCreateInternal:37-41注册HotspotUtils.Callback+ 主动 updateEnableStateupdateEnableState:55-61异步查HotspotUtils.isSoftApEnable()(tetheredSoftApState==ENABLED)hasPaused标记(onPauseInternal:64-67 置 true,onResumeInternal:44-49 主动刷新)避免回调丢失导致状态不同步- 直接实现
HotspotUtils.Callback(与 A 套 WifiTetheringHandler 回调链不同)
8. 设计要点与坑
- 双 ViewModel 命名陷阱:
hotspot/HotSpotViewModel.kt(A)vshotspot/viewmodel/HotspotViewModel.kt(B)类名几乎一致、不同包,import 错 LiveData 不更新,务必核对全路径。 - 两套重启机制不感知:mRestartBooked(A)与 mIsNeedReopen(B)分属不同对象,跨实例可能重复 start。
- 配置改 SSID/密码不立即生效:必须”关→改→开”一轮,期间开关 loading 不可点;HAL 回调丢失会卡关闭态,B 套 onResume 主动刷新兜底。
- 频段强制覆写:用户只改 SSID/密码,频段/隐藏性/自动关机不受控,开放需改 configSoftApBand。
- 客户端实时性:仅回调时更新,无轮询,mainExecutor;B 套进入时 initData 读一次兜底。内存泄漏:
InnerCallback/CustomMicarSoftApCallback均 WeakReference(native unregister 后仍持有引用)。 - HotspotClientInfo equals/hashCode:基于 MAC,否则 AsyncListDiffer ConcurrentHashMap 出现”Unknown 丢失/REDMI 重复”(2026-05-20 AI 补的修复)。
- 设备名乱码兜底:
HotspotUtils.getSoftApName:152-159+isGarbledCode:166-179,hostname 非字母数字/非中文判乱码回退 macAddress。 - WifiTetheringHandler 双重职责:既是状态机中枢又是配置入口,还转发 registerSoftApCallback;自承被两处使用(历史包袱),setWifiTetheringAvailabilityListener 可能多次调用。
关键调用点速查
| 关注点 | 位置 |
|---|---|
| startTethering(A) | WifiTetheringHandler.java:204-206 |
| openHotspot(B) | HotspotUtils.kt:211-213 |
| setSoftApConfiguration | WifiTetheringHandler:212-217(A)/ HotspotUtils.kt:265-278(B) |
| 频段强制覆写 | WifiTetheringHandler.configSoftApBand:225-249 |
| mRestartBooked | WifiTetheringHandler.java:186-193 |
| mIsNeedReopen | HotspotUtils.kt:51-54 |
| QR 串格式 | HotspotQrCodeUtils.getQrcodeText:117-135 |
| IHotspotRouter 路由 | view/HotspotSettingActivity.kt:222-257 |
| 连接主页入口状态 | WifiTetherStatusController.updateState:75-80 |