01 · 连接主页架构与路由
入口 Fragment:
connection/view/ConnectionSettingsTopFragment.java页面 XML:res/xml/connection_settings_fragment.xml路径相对settingsPage/micarConnectionSettings/src/main/
1. 在车设中的定位
连接页是车设 13 个一级页面之一,与车辆控制/灯光/门窗锁/驾驶/辅助驾驶/安全/能量/显示/声音/IoT/语音/服务/系统 并列。
- 菜单声明:
app/src/region/{cn|global}/res/xml/miauto_top_level_menu_fragment.xml:79-86- key:
@string/psk_settings_connection_entry - fragment:
com.android.car.settings.miauto.connection.view.ConnectionSettingsTopFragment settings:hiddenFeatures="@string/micar_common_settings_lemans_ece"→ Lemans ECE 车型隐藏入口
- key:
- 页面别名:
PageAlias.PAGE_CONNECTION = "connection"(base/settingsBaseUi/.../common/pageroute/PageAlias.kt:31),PageRouterConfig.kt:22映射到R.string.psk_settings_connection_entry
2. 继承链与注解
classDiagram direction TB class SettingsFragment class BaseXmlParserSettingsFragment { <<abstract>> +List~Integer~ getPreferenceKeyResIdsToRemove() #removePreference(int keyResId) } class TopLevelSettingsFragment { <<abstract 一级页通用能力>> -左侧 margin 136dp / 顶部 230dp -锚点跳转动画 / Folme 弹簧滚动 +getPageAlias()* +getPreferenceScreenResId()* } class ConnectionSettingsTopFragment { +getRightFragment() → ConnectionSettingsRightFragment +getPageAlias() = "connection" +getPreferenceScreenResId() = R.xml.connection_settings_fragment +getPreferenceKeyResIdsToRemove() -gatherController() -notifyConnectStateChange(ConnectInfo) } class BaseRightFragment { <<右侧车模容器>> +int getInitCarModelImgRes()* } class ConnectionSettingsRightFragment { +getInitCarModelImgRes() 雷达/卡钳 4 图 } SettingsFragment <|-- BaseXmlParserSettingsFragment BaseXmlParserSettingsFragment <|-- TopLevelSettingsFragment TopLevelSettingsFragment <|-- ConnectionSettingsTopFragment BaseRightFragment <|-- ConnectionSettingsRightFragment
基类 TopLevelSettingsFragment(base/settingsBaseUi/.../miauto/TopLevelSettingsFragment.java)封装一级页通用能力。BaseXmlParserSettingsFragment 提供 getPreferenceKeyResIdsToRemove() 模板方法(:1083-1091 的 removePreferencesIfNeed 在 Preference 树构建后逐个 removePreference)。
三大注解(ConnectionSettingsTopFragment.java:57-60)
| 注解 | 定义 | 作用 |
|---|---|---|
@VoiceSearchProvider | settingsCommon/plugin/voice/voicesearchannotation/ | 标记参与语音搜索;APT VoiceSearchProcessor 反射调 getPreferenceScreenResId()/getPageAlias()/getPreferenceKeyResIdsToRemove() 生成索引。约束:反射发生在 Fragment 未 attach 时,getPreferenceKeyResIdsToRemove() 内严禁调 getContext()/getString()(见 BaseXmlParserSettingsFragment.java:1064-1074) |
@Module(TAG_CONNECTION) | settingsCommon/plugin/router/annotation/Module.java | 注册到路由模块表,SettingsConstant.TAG_CONNECTION = "micarConnectionSettings"。须在 SettingsApplication.java:34 的 @Modules({...}) 列出才会被 Router.merge() 合并 |
@MonitorFragment | com.mi.car.dfx_sdk.MonitorFragment(外部 SDK) | DFX 埋点标记,13 个一级 Fragment 都加 |
3. 页面结构(connection_settings_fragment.xml)
4 个 MiCarPreferenceCategory + 若干 SpacePreference 间隔。Entry 详见 00-README 四大类入口表。其中:
- 蓝牙/WLAN/热点 通过 静态 XML
<intent android:action=.../>跳转,controller 只管状态显示 - 手车互联 4 项 无静态 intent,靠 controller 的
doConnect()发 URI - 蜂窝网络
NewTabPreference,Tab 内完成切换 - 卫星 通过 intent
android.settings.SATELLITE_COMMUNICATE
flowchart TB subgraph Top["ConnectionSettingsTopFragment"] C1["① 基础连接<br/>蓝牙·WLAN·热点"] C2["② 手车互联<br/>小米互联·CarPlay·CarLink·AndroidAuto"] C3["③ 蜂窝网络<br/>优先运营商(DCD隐藏)"] C4["④ 移动网络<br/>卫星通信(无能力隐藏)"] end
4. 动态显隐机制(getPreferenceKeyResIdsToRemove)
ConnectionSettingsTopFragment.java:168-199。三类”能力探测器”:
- 包名探测
ConnectionUtils.kt:35-59→PackageUtils.isPkgInstalled - 平台探测
DeviceUtil.java:226-228→ro.board.platform == "msmnile" - 卫星探测
TelephonyUtil.isSupportSatelliteByDevice→ 反射TelephonyManagerEx(flavor 源码)
flowchart TD Start([getPreferenceKeyResIdsToRemove]) Q1{isPhoneCarLinkSupported?<br/>4 互联 app 任一预装} Q1 -- 否 --> R0[删 整个手车互联 Category<br/>+ vehicle_network_category_space] Q1 -- 是 --> Q2{逐项: MiuiShare/CarPlay/CarLink/AA<br/>不支持则删对应 entry} Q1 & Q2 --> Q6{DeviceUtil.isDCDPlatform?} Q6 -- 是 --> R5[删 蜂窝网络 Category + space] Q6 --> Q7{isSupportSatelliteByDevice?} Q7 -- 否 --> R6[删 移动网络 Category<br/>= 整个卫星分组] End([return idsToRemove]) R0 & R2 & R5 & R6 --> End
| 行号 | 条件(不成立时) | 删除的 key |
|---|---|---|
| 171-173 | !isPhoneCarLinkSupported() | pk_connection_complex_category + pk_connection_vehicle_network_category_space |
| 175-186 | 逐项 !isXxxSupported() | 对应 miui_share/carplay/carlink/android_auto entry |
| 190-193 | isDCDPlatform() | pk_connection_vehicle_network_category + space(注释:XCD 默认双卡) |
| 195-197 | !isSupportSatelliteByDevice() | pk_connection_mobile_network(整个卫星分组) |
耦合点:pk_connection_vehicle_network_category_space 在”全无互联”和”DCD 平台”两个分支都被删,改动时注意。
5. 路由与跳转
连接页如何被加载:SettingsApplication.java:28-37 注册模块 → APT 生成 RouterHelper.merge() → Router.java 合并路由表 → 主菜单 Fragment 加载 XML,用户点击时 Preference 框架 onPreferenceStartFragment() 自动实例化 ConnectionSettingsTopFragment。外部 URI 经 Router.dispatcher(uri) 暂存,Fragment onResume 匹配路由表分发。
子入口跳转:
| 入口 | 方式 | 目标 |
|---|---|---|
| 蓝牙 | intent BLUETOOTH_SETTINGS | BluetoothSettingsActivity |
| WLAN | intent WIFI_SETTINGS(+extra refer) | WifiSettingsActivity |
| 热点 | intent HOTSPOT_SETTINGS | HotspotSettingActivity / MiCarHotspotSettingsActivity(两套) |
| 小米互联 | URI mis://complex/connect?action=mirror_setting | 外部 app(点标题图标走 mirror_guide 引导) |
| CarPlay/CarLink/AA | URI mis://complex/connect?action={carplay,carlink,android_auto}_connect | 外部 app |
| 蜂窝 | Tab 内 TelephonyUtil.setDefaultChannel | 无外部跳转 |
| 卫星 | intent SATELLITE_COMMUNICATE | SatelliteSettingsActivity |
URI 常量在 ConnectionConstants.kt:5-10,前缀 mis://complex/connect?action=,由目标 app 自己的 Activity 捕获(Settings 仅发 ACTION_VIEW,无 fallback UI,ActivityNotFoundException 只打日志)。
6. 手车互联状态分发(MisComplexSdk)
MisComplexSdk 是外部 aar com.xiaomi.phonecarlink:complex:0.1.40,对 IMisComplex AIDL service 的封装,实际连接业务跑在外部 app 进程。init(ctx, false, false) 在 app/.../AsyncThreadStartTask.java:217-220(两 bool = withMirror/withCarWith,Settings 都传 false,仅作 listener/caller)。
sequenceDiagram participant App as 外部 app 进程<br/>(com.mi.car.carplay 等) participant Aar as MisComplexSdk aar participant Top as ConnectionSettingsTopFragment participant Ctl as BaseVehicleLinkPreferenceController[4] participant UI as BasicPreference Top->>Aar: registComplexConnectListener (onCreate:138) Note over App: 手机连接 CarPlay App->>Aar: onComplexConnected(ConnectInfo{type=CARPLAY,...}) Aar->>Top: onComplexConnected(info) (:127) Top->>Top: notifyConnectStateChange(info) 遍历 (:141-148) loop 每个 controller Top->>Ctl: onConnectStateChange(type, info) Ctl->>Aar: getConnectInfo() (:59) alt info.type == controller.getCurrentConnectType() Ctl->>UI: updateRightText(displayName) else 不匹配 Ctl->>UI: updateRightText("") end end Note over App: 服务挂掉 App->>Aar: onBinderDied() Aar->>Top: onBinderDied (:121) → notifyConnectStateChange(null) Note over UI: 所有 entry 右侧文案清空
关键设计:updateConnectStatus 用全局 getConnectInfo() 而非回调传入的 info(BaseVehicleLinkPreferenceController.java:57-67),前提假设同一时刻只有一种互联 active。onServiceDisconnected/onBinderDied 传 null → 全清空。onResumeInternal/onCreateInternal 也各刷一次保证回页即同步。
7. 右侧车模(ConnectionSettingsRightFragment)
ConnectionSettingsRightFragment.kt:13-41,根据两项车控配置选车模图:
MiCarSettings.isCfgRadarUIEnable()(雷达)/isCfgCaliperPainted()(卡钳)
| 雷达 | 卡钳 | 资源 |
|---|---|---|
| ✓ | ✓ | micar_car_model_connection_settings(默认,含 Kunlun) |
| ✓ | ✗ | ..._low_have_radar_no_caliper |
| ✗ | ✓ | ..._low_no_radar_have_caliper |
| ✗ | ✗ | ..._low_no_radar_no_caliper |
isUseDefaultCarModel() 含 DeviceUtil.isKunLun() 分支,Kunlun 强制默认图。
8. 设计要点与坑
- 能力探测耦合:包名/平台/卫星判断全集中在
getPreferenceKeyResIdsToRemove()一个方法(:168-199)。 PackageUtils.isPkgInstalled局限(PackageUtils.java:87-111):不带MATCH_UNINSTALLED_PACKAGES,disabled 但 installed 的包仍判”支持” → 可能入口可见但点击无响应(ActivityNotFoundException被 catch 无用户反馈)。- lazy 不刷新:互联能力
by lazy进程内只查一次,安装/卸载 app 后需重启 Settings 入口才更新。 - 锚点功能:
TopLevelSettingsFragment.isAnchorAvailable()要求isTopLevelPageByAlias() && isAfterKunlunVehicleStage(E4U4_2),连接页仅在 Kunlun E4U4.2+ 显示分组快速跳转锚点。 - 遗留 key:
preference_keys.xml:10-11的pk_connection_wireless_charge_entry、pk_connection_mobile_to_vehicle_entry已声明但未在 connection_settings_fragment.xml 使用(疑似遗留)。 - ⚠️ 双 flavor TelephonyUtil:
src/{xcddif,dcddif}/.../TelephonyUtil.kt,DCD 版isSupportSatelliteByDevice反射 receiver 传错(见 对抗记录 Bug 1)。 - ⚠️ manifest SATELLITE 拼写:
AndroidManifest.xml:109,113同时注册COMMUNICATE+COMMUNITE(见对抗记录 Bug 3)。