个人热点全流程

模块根:settingsPage/micarConnectionSettings/src/main/java/com/android/car/settings/miauto/hotspot/

0. 关键结论(高频踩坑)

  1. 存在两套并行实现!改 A 不影响 B,排查前必先确认入口:
    • A 套MiCarHotspot*(大写 S)+ WifiTetheringHandler + hotspot/HotSpotViewModel + TetherModifyDialogHelper
    • B 套Hotspot* + HotspotUtils + viewmodel/HotspotViewModel + HotspotConfigChangeDialog
  2. 两套”配置变更后重启”机制:WifiTetheringHandler.mRestartBooked(:186-193)vs HotspotUtils.mIsNeedReopen(:46-55,275)
  3. 真正 startTethering 实质只有一处:mMiCarWifiManager.startMicarSoftAp(MicarWifiManager.TETHERED_SOFTAP),被 A 套 WifiTetheringHandler.startTethering():204-206 和 B 套 HotspotUtils.openHotspot():211-213 各封装。不用 WifiManager.startTethering/TetheringManager
  4. 客户端列表靠 MicarSoftApCallback(非标准 TetheringEventCallback),弱引用防泄漏。
  5. 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@RouterProvider on MiCarHotSpotFragment);B 套走 android.settings.HOTSPOT_SETTINGS Intent / 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)
ENABLINGdisablePreference(开关 loading+不可点)isLoading=true, isEnabled=false
ENABLEDenablePreference + onWifiTetheringAvailable→VM postValue(true)开关勾选、occupancy 隐藏、container 显示
DISABLINGdisablePreference + onWifiTetheringUnavailableUI 先行:取消勾选、container 隐藏
DISABLEDonWifiTetheringUnavailable + enablePreference;mRestartBooked→再 startTethering开关不勾、dismiss 配置弹窗

开关调用点

  • A 套:TetherSwitchController.onCreateInternal:30-33HotSpotViewModel.setTetheredSoftApState(true):145-153WifiTetheringHandler.startTethering():204-206startMicarSoftAp(TETHERED_SOFTAP)
  • B 套:HotspotSettingActivity.initView:93-103 setOnCheckedChangeListener → 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-312auto_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/密码/频段/安全)

持久化

SoftApConfigurationWifiManager.setSoftApConfiguration(framework 持久化):

  • A 套:HotSpotViewModel.modifyHotSpot:121-134WifiTetheringHandler.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.openNetShareSettings.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+名称+已连接数
HotspotForCarplayFragmentgetLocalOnlyHotspotConfiguration().ssid(:37-41)CarPlay 引导,展示 LocalOnlyHotspot SSID
HotspotForMiConnectionFragmentDeviceUtil.getDeviceName(:22-24)小米互联引导,展示车机名
HotspotConnectedFragmentconnectedDevices已连接客户端列表

本质区别:CarPlay 取 LocalOnlyHotspotConfiguration(设备互联热点 LOHS,无外网),MiConnection 取 DeviceUtil.getDeviceName(车机名)。普通态展示 TetheredSoftAp(公共热点)。4 入口均用户点击触发,showFragment:263-282 固定 replace+addToBackStack。

LocalOnlyHotspot vs TetheredSoftApMicarWifiManager.TETHERED_SOFTAP=公共热点;LOHS=设备互联热点无外网,HotspotUtils.getLocalOnlyHotspotConfiguration:291-293 读取,仅 CarplayFragment 用。两者配置独立、客户端回调独立(LOHS 回调在两 VM 中均空实现)。

7. WifiTetherStatusController(连接主页热点入口状态)

controller/WifiTetherStatusController.java,挂连接主页入口项,仅显示”已开启/已关闭”文案。

  • onCreateInternal:37-41 注册 HotspotUtils.Callback + 主动 updateEnableState
  • updateEnableState:55-61 异步查 HotspotUtils.isSoftApEnable()(tetheredSoftApState==ENABLED)
  • hasPaused 标记(onPauseInternal:64-67 置 true,onResumeInternal:44-49 主动刷新)避免回调丢失导致状态不同步
  • 直接实现 HotspotUtils.Callback与 A 套 WifiTetheringHandler 回调链不同

8. 设计要点与坑

  1. 双 ViewModel 命名陷阱hotspot/HotSpotViewModel.kt(A)vs hotspot/viewmodel/HotspotViewModel.kt(B)类名几乎一致、不同包,import 错 LiveData 不更新,务必核对全路径。
  2. 两套重启机制不感知:mRestartBooked(A)与 mIsNeedReopen(B)分属不同对象,跨实例可能重复 start。
  3. 配置改 SSID/密码不立即生效:必须”关→改→开”一轮,期间开关 loading 不可点;HAL 回调丢失会卡关闭态,B 套 onResume 主动刷新兜底。
  4. 频段强制覆写:用户只改 SSID/密码,频段/隐藏性/自动关机不受控,开放需改 configSoftApBand。
  5. 客户端实时性:仅回调时更新,无轮询,mainExecutor;B 套进入时 initData 读一次兜底。内存泄漏:InnerCallback/CustomMicarSoftApCallback 均 WeakReference(native unregister 后仍持有引用)。
  6. HotspotClientInfo equals/hashCode:基于 MAC,否则 AsyncListDiffer ConcurrentHashMap 出现”Unknown 丢失/REDMI 重复”(2026-05-20 AI 补的修复)。
  7. 设备名乱码兜底HotspotUtils.getSoftApName:152-159+isGarbledCode:166-179,hostname 非字母数字/非中文判乱码回退 macAddress。
  8. WifiTetheringHandler 双重职责:既是状态机中枢又是配置入口,还转发 registerSoftApCallback;自承被两处使用(历史包袱),setWifiTetheringAvailabilityListener 可能多次调用。

关键调用点速查

关注点位置
startTethering(A)WifiTetheringHandler.java:204-206
openHotspot(B)HotspotUtils.kt:211-213
setSoftApConfigurationWifiTetheringHandler:212-217(A)/ HotspotUtils.kt:265-278(B)
频段强制覆写WifiTetheringHandler.configSoftApBand:225-249
mRestartBookedWifiTetheringHandler.java:186-193
mIsNeedReopenHotspotUtils.kt:51-54
QR 串格式HotspotQrCodeUtils.getQrcodeText:117-135
IHotspotRouter 路由view/HotspotSettingActivity.kt:222-257
连接主页入口状态WifiTetherStatusController.updateState:75-80