06 · 多 Flavor 与海外适配
一、三 Flavor 是什么(关键澄清)
dcd / xcd / global 不是 debug/release,是两套车机硬件平台 + 海外地区组合。
| Flavor | 平台 | 地区 | 面向 |
|---|---|---|---|
| dcd | DCD 平台(ro.board.platform == "msmnile") | 国内 | 国内老一代车机(“watt” 软件分支) |
| xcd | XCD 平台(新一代,非 msmnile) | 国内 | 国内新一代车机 |
| global | 复用 XCD 二进制(jar 与 xcd 完全相同) | 海外 | 海外版(ECE 认证区、欧洲多语言) |
源码只有
PLATFORM_MSMNILE = "msmnile"(DeviceUtil.java:19),「骁龙 8155」是业界对 msmnile 的通常对应,源码未直接写明型号。 检测:DeviceUtil.isDCDPlatform()=PLATFORM_MSMNILE.equals(getPlatform())。
二、平台差异如何屏蔽(编译期 jar 切换,不是源码抽象)
核心机制:dcd 和 xcd 各有一份独立编译的 framework.jar / android.car.jar,编译期按 task 名前置到 bootstrapClasspath:
// constants.gradle
dcd_frameworkJarPath = ".../javalib_watt_250721.jar" // DCD: watt 分支
xcd_frameworkJarPath = ".../xcd_javalib_1129.jar" // XCD: xcd 命名前缀
dcd_car_ui_lib_path = ".../android.car_watt0911.jar"
xcd_car_ui_lib_path = ".../xcd_android.car_1129.jar"// dependencies.gradle afterEvaluate:自动给每个 Android 子模块注入 flavor 专属 compileOnly
deps.add("dcdCompileOnly", files(rootProject.dcd_framework_lib_path))
deps.add("xcdCompileOnly", files(rootProject.xcd_framework_lib_path))
deps.add("globalCompileOnly", files(rootProject.xcd_framework_lib_path)) // global 复用 xcd// build.gradle gradle.projectsEvaluated:按 task 名把对应 jar 前置到 bootstrapClasspath
if (currentFlavor.contains("Dcd")) newFileList.add(new File(DCD_FRAMEWORK_JARPATH))
else if (currentFlavor.contains("Xcd") || currentFlavor.contains("Global"))
newFileList.add(new File(XCD_FRAMEWORK_JARPATH))约束:源码只用符号常量(如 MiCarPropertyIds.LockCtrl.WALK_AWAY_LOCK),不写魔法数字,不写 if (flavor==dcd)。平台差异写到 src/dcddif 或 src/xcddif。
三、sourceSet 拼图(源码核实)
app 模块(app/build.gradle)
| Flavor | Manifest | java | res | aidl |
|---|---|---|---|---|
| dcd | src/main/AndroidManifest.xml | main + dcddif | main + dcddif + region/cn | main + dcddif |
| xcd | src/main/AndroidManifest.xml | main + xcddif | main + xcddif + region/cn | main + xcddif |
| global | src/region/global/AndroidManifest.xml(换文件) | main + xcddif + region/global | main + xcddif + region/global | main + xcddif |
page 模块(以 micarChargeSettings/build.gradle 为准)
| Flavor | java | res |
|---|---|---|
| dcd | main + dcddif | main + dcddif |
| xcd | main + xcddif | main + xcddif |
| global | main + xcddif + global | main + xcddif + global |
命名差异(易混):app 模块用
src/region/global/,page 模块用src/global/。 当前app/src/dcddif、xcddif实际只有res/raw/service_center_provider_info.json一个文件,没有 Java。
四、五种差异化手段(隔离强度从强到弱)
| # | 手段 | 时机 | 隔离粒度 | 示例 |
|---|---|---|---|---|
| 1 | sourceSet 分目录 | 编译期 | 文件级 | 海外专属 Java/资源放 region/global |
| 2 | globalImplementation 依赖 | 编译期 | 模块级 | settingsPage/globalOnly 只进 global APK |
| 3 | BuildConfig.FLAVOR | 编译期常量 | if 分支 | BuildConfig.FLAVOR.contains("dcd") 平台判断 |
| 4 | DeviceUtil.isXxxRegion() | 运行时 | if 分支 | 基于 ro.micar.build.region(cn/global/eu/ece)地区判断 |
| 5 | settings:hiddenFeatures | 运行时 | 单个 Preference | ECE 合规隐藏(HiddenFeaturesKeyListHelper 解析) |
编码规范优先级:sourceSet / globalImplementation(编译期)> hiddenFeatures XML 标记 > DeviceUtil / BuildConfig if 分支。
五、globalOnly 模块(海外专属,双重保险)
5.1 限定只在 global 编译
第一道:app 端 globalImplementation(app/build.gradle:168)
globalImplementation project(":settingsPage:globalOnly") // 只进 global APK第二道:globalOnly 自己 sourceSet 的 dcd/xcd 为空(globalOnly/build.gradle)
sourceSets {
dcd { } // 空
xcd { } // 空
global { manifest.srcFile 'src/main/AndroidManifest.xml'; java.srcDirs = ['src/main/java']; ... }
}5.2 内容(来自 settingsPage/globalOnly/readme.md)
| 子包 | 功能 |
|---|---|
privacy/ | 海外合规隐私(麦克风/摄像头/车辆数据):PrivacySettingsFragment + 20+ Controller |
location/ | 海外位置设置:LocationSettingsFragment |
security/ | 锁屏 PIN(设置/验证/开机引导):LockPinVerifyActivity、LockPinSetActivity、ProvisionLockPinSetActivity |
setupservice/ | InitialLockSetupService(开机引导锁屏) |
bluetooth/ | BluetoothRequestPermissionActivity(GTS 测试) |
common/ | Google Settings Inject:ExtraSettingsLoader |
特点:globalOnly 不依赖 settingsVehicleLib、不用 routerApt/voicesearchapt(不走路由/语音体系),只依赖 settingsBaseLib/settingsBaseUi/settingsLibAndroid/settingsLibTile。
六、Manifest 差异(国内 vs 海外)
src/main/AndroidManifest.xml(1875 行)与 src/region/global/AndroidManifest.xml(1904 行)是两份独立完整 Manifest,非增量合并:
| 类别 | 国内(main) | 海外(global) |
|---|---|---|
| 国内专属权限 | mi.car.settings.permission.ATMOSPHERE_LIGHT_SYNCHRONIZE | 移除 |
| 国内专属弹窗 | 强制发电/强制纯电 DialogActivity | 移除 |
| 国内专属 Service | SettingMisModulePublishService(MIS 车控) | 移除 |
| 海外新增 Activity | — | Location/Privacy/Microphone/Camera SettingsActivity |
| 海外新增 Service | — | InitialLockSetupService |
| 海外 PIN 流程 | — | LockPinVerify/SetActivity、ProvisionLockPinSetActivity |
| Activity 主题 | DarkFullscreenTheme | DarkFullscreenThemeKeepStatusBar(tools:replace) |
⚠️ 改公共组件必须两边 Manifest 都改。
七、首页菜单差异
国内 region/cn/res/xml/miauto_top_level_menu_fragment.xml(14 项带 fragment)vs 海外 region/global/...(16 项):
| 菜单项 | 国内 | 海外 |
|---|---|---|
| 辅助驾驶 | AutopilotSettingsFragment | GlobalAutopilotSettingsFragment |
| 位置 | 无 | LocationSettingsFragment |
| 隐私 | 无 | PrivacySettingsFragment |
Google 设置项在代码中由
TopLevelMenuFragment.getPreferenceKeyResIdsToRemove()国内移除,仅海外显示。
八、i18n 资源
| values 目录 | 语言 |
|---|---|
values/ | 默认(英文兜底) |
values-zh-rCN/ | 简中 |
values-en-rGB/ | 英文(海外主语言) |
values-de/-fr/-it/-es/-nl/-nb/-sv/ | 欧洲 7 种 |
- 欧洲 8 种语言主要在
base/settingsBaseLib、base/settingsBaseUi(齐全)。 - 不用 RRO(Runtime Resource Overlay),纯 sourceSet 覆盖 + flavor 资源。
- 翻译流程:开发只改
values/或values-zh-rCN/,其他语言由 atlas 平台自动回填。
九、构建产物标识
三 flavor 的 applicationId 相同(com.android.car.settings),靠 Maven publication 区分:
dcd → com.mi.car:MiCarSettings:<version>-dcd@apk
xcd → com.mi.car:MiCarSettings:<version>-xcd@apk
global → com.mi.car:MiCarSettings:<version>-global@apk