07 · 功能页面模块架构(settingsPage)

16 个 settingsPage 模块共享高度统一的约定。本文给出标准范式,新增功能页照此写。

一、统一页面组织模式

1.1 承载方式:PreferenceFragment(micarx.preference)

所有功能页统一用 PreferenceFragment 承载:Fragment 加载一个 XML(getPreferenceScreenResId()),XML 声明每个 <Preference> 及其 settings:controller="XxxController",Controller 负责数据刷新与点击。

继承链(最终都到 BaseXmlParserSettingsFragment):

BaseXmlParserSettingsFragment (核心, 1562 行)
  ├─ SettingsFragment → TopLevelSettingsFragment  (一级页基类)
  │     ├─ EnergyManagerFragment        (充电)
  │     ├─ LightsSettingsFragment       (灯光)
  │     ├─ UniversalSettingsFragment    (系统)
  │     └─ IotSettingsFragment          (IoT)
  └─ SecondLevelSettingsFragment  (二级页基类)

1.2 一级 Fragment 四件套注解(标配)

@VoiceSearchProvider                    // 语音搜索可索引
@Module(SettingsConstant.TAG_CHARGE)    // 路由模块归属
@RouterProvider(path = ENERGY_MANAGER_URI_PATH)  // 外部深链路由
@MonitorFragment                        // 埋点
class EnergyManagerFragment : TopLevelSettingsFragment(), IPageRouteHandler {
    override fun getPageAlias() = PageAlias.PAGE_ENERGY_MANAGER
    override fun getPreferenceScreenResId() = R.xml.miauto_energy_manager_fragment
}

1.3 双栏模型

首页左右双栏,右栏由 BaseRightFragment(实现 CarModelManager)承载(车型图、版本号)。一级 Fragment 可重写 getRightFragment() 指定右栏内容(如 UniversalSettingsFragment 返回 MiCarVersionRightFragment)。

二、标准目录结构(以 micarChargeSettings 为模板)

micarChargeSettings/
├── build.gradle              # 统一模板
├── Android.bp                # AOSP 构建(与 build.gradle 并存)
├── libs/                     # 本地 jar
└── src/
    ├── main/
    │   ├── AndroidManifest.xml
    │   ├── java/
    │   │   ├── com/android/micar/settings/energy/      ← 业务包(新版包名)
    │   │   │   ├── EnergyManagerFragment.kt            (一级 Fragment)
    │   │   │   ├── EnergyManagerRightFragment.kt       (右栏)
    │   │   │   ├── controller/                         (PreferenceController)
    │   │   │   ├── view/                               (自定义 View/Dialog/Activity)
    │   │   │   ├── viewmodel/
    │   │   │   └── utils/
    │   │   └── com/android/micar/settings/preference/  (自定义 Preference 控件)
    │   └── res/
    │       ├── xml/            ← 核心:PreferenceFragment 加载的菜单 XML
    │       │   └── miauto_energy_manager_fragment.xml
    │       ├── layout/ drawable/ values/ values-zh-rCN/ values-de/ ...
    │       └── drawable-night/ values-night/
    ├── dcddif/                 (dcd 差异: java/res)
    ├── xcddif/                 (xcd 差异: java/res)
    └── global/                 (海外差异: java/res,约定存在)

包名两套并存(历史演进)

版本包名模块
新版com.android.micar.settings.<feature>energy(charge)、iot、autopilot
历史版com.android.car.settings.miauto.<feature>display、volume、lights、driving、vehicle、safetyservice

新功能走 com.android.micar.settings

三、build.gradle 通用模板

plugins {
    id 'com.android.library'
    id 'org.jetbrains.kotlin.android'
    id 'kotlin-kapt'
}
 
android {
    flavorDimensions = ["miPlatform"]
    productFlavors { dcd {}; xcd {}; global {} }
    sourceSets {
        dcd    { java.srcDirs = ['src/main/java', 'src/dcddif/java']; res.srcDirs = ['src/main/res', 'src/dcddif/res'] }
        xcd    { java.srcDirs = ['src/main/java', 'src/xcddif/java']; res.srcDirs = ['src/main/res', 'src/xcddif/res'] }
        global { java.srcDirs = ['src/main/java', 'src/xcddif/java', 'src/global/java']
                 res.srcDirs = ['src/main/res', 'src/xcddif/res', 'src/global/res'] }
    }
}
 
dependencies {
    implementation project(":base:settingsBaseUi")        // Fragment/Controller 基类
    api project(":base:settingsBaseLib")                   // 核心工具
    implementation project(":base:settingsVehicleLib")     // 车辆信号
    kapt project(":settingsCommon:plugin:router:routerApt")        // 路由表生成
    kapt project(":settingsCommon:plugin:voice:voicesearchapt")    // 语音索引生成
}

例外globalOnly 不依赖 settingsVehicleLib,不用 routerApt/voicesearchapt(海外隐私/位置/锁屏不走路控体系)。

四、数据来源(三种)

数据来源典型模块入口
CarProperty(车辆信号)charge / display / light / lock / driving / volumeSettingsCarPropertyManager
IoT 中心 SDKmicarIotSettingsIotClientManager(settingsBaseLib,监听设备卡片,不经 CarProperty
系统服务/配置settingsSystemCarConfigManagerBuildNumberPreferenceController

五、完整链路(充电「在途加热开关」为例)

[XML 声明]                         [Controller 层]                              [车辆信号层]
miauto_energy_manager_fragment.xml
  <MiCarNewSwitchPreference              EnergyHeatingSwitchController
    settings:controller=                  extends BaseVehicleNewSwitchPrefController
    "...EnergyHeatingSwitchController">        extends CarPropertyMgrPreferenceController<V>
    android:key="@string/pk_..."                  implements CarPropertyManagerCallback
                                                       │ 持有
                                                SettingsCarPropertyManager   (settingsVehicleLib)
                                                       │
                                                VehicleControlManager
                                                       │
                                                android.car.CarPropertyManager ──Binder──► CarService ──► HAL

5.1 XML 绑定 Controller

<MiCarNewSwitchPreference
    android:key="@string/pk_energy_heating_on_the_way"
    android:title="@string/micar_energy_manager_heating_on_the_way_title"
    settings:controller="com.android.micar.settings.energy.controller.EnergyHeatingSwitchController" />

5.2 Controller 声明监听哪个信号

class EnergyHeatingSwitchController(...) : BaseVehicleNewSwitchPrefController(...) {
    override fun getPropertyIdSet() = ArraySet<Int>().apply { add(HEATING_ON_THE_WAY) }
    override fun isPropertyOpen(propVal: Int) = propVal in arrayOf(ON, ...ACTIVE)
}

5.3 端到端 8 步

  1. 冷启动SettingsApplicationHomePageActivityReal 加载 TopLevelMenuFragment
  2. 菜单渲染TopLevelMenuFragment inflate miauto_top_level_menu_fragment.xml
  3. 点击菜单:「充电&能量」→ 按 android:fragment="...EnergyManagerFragment" 实例化,加入右栏
  4. Fragment 初始化:父类解析 XML,反射创建 Controller,绑定 Preference
  5. Controller 订阅onCreateInternal()registerPropertyCallback(getPropertyIdSet())
  6. 回流刷新:信号变化 → onPropertyChanged → 更新 MiCarNewSwitchPreference
  7. 用户改设置handlePreferenceChangedsetCarProperty(HEATING_ON_THE_WAY, ...) 下发
  8. 外部深链(旁路):carsettings://homepage/?subPage=energy_manage → Router → EnergyManagerFragment.handleJump(uri)

六、车型差异的运行时控制

页面显隐还由 CarConfigManager(CCP 配置字)控制。例:

// EnergyManagerFragment.kt:75-96
override fun getPreferenceKeyResIdsToRemove(): List<Int> {
    val remove = mutableListOf<Int>()
    if (!CarConfigManager.isEceSocketType()) remove.add(R.string.pk_xxx)
    if (CarConfigManager.getRoofRackPowerConfig() == 0) remove.add(R.string.pk_yyy)
    return remove
}

七、16 个模块速查表

维度约定
一级页基类TopLevelSettingsFragment
二级页基类SecondLevelSettingsFragment
右栏基类BaseRightFragment
车控 Controller 基类BaseVehicleNewSwitchPrefController / BaseVehicleTabPreferenceController
页面项注册XML:<Preference settings:controller="X"/>
菜单注册app/src/region/{cn,global}/res/xml/miauto_top_level_menu_fragment.xmlandroid:fragment
外部深链@RouterProvider(path=...) + IPageRouteHandler,path 在 CarSettingsJump.kt
别名常量PageAlias.kt
车控数据SettingsCarPropertyManager
IoT 数据IotClientManager(不经 CarProperty)
车型差异dcddif/ / xcddif/ / global/ sourceSet + CarConfigManager 运行时
海外专属globalOnly 模块(仅 globalImplementation
build.gradle三 flavor + BaseUi/BaseLib/VehicleLib + routerApt/voicesearchapt kapt

八、新增功能模块 checklist

  1. 通常复用现有 settingsPage/*,不新建模块。
  2. 确需新建:settings.gradleinclude + SettingsApplication.@Modules 加模块名(漏了路由静默失效)。
  3. 新建 Fragment:继承 TopLevelSettingsFragmentSettingsFragment,实现 IPageRouteHandler
  4. 加四件套注解。
  5. 加常量:PageAlias.ktPAGE_XXXCarSettingsJump.ktXXX_URI_PATH
  6. 新建 XML res/xml/xxx_fragment.xml
  7. 新建 Controller(车控继承 BaseVehicleNewSwitchPrefController,构造器 4 参)。
  8. 多语言:values/ + values-zh-rCN/ + values-en-rGB/ 三处。
  9. 父页面加入口(XML Preference + 入口 Controller)。
  10. ./gradlew checkStyleCode 自查 → 提交。

完整 16 条 checklist + 12 条踩坑清单见现有 docs/08-开发实战-新增设置页.md