MiCarSettings 打包教程
本篇是「照着做就能打出包」的实操教程。覆盖 本地 Gradle 打包、编进系统镜像(Soong)、CI 发版 三条链路,含完整命令、产物路径、流程图与踩坑 FAQ。 所有结论均来自真实脚本/源码阅读,引用格式为
文件路径:行号。
〇、读完你能掌握什么
- 这个 App 的「身份」——为什么它不是普通可卸载应用,打包时要受什么约束。
- 三条打包路径分别在什么时候用、命令是什么、产物在哪。
- 三 Flavor(dcd / xcd / global)怎么叠加源码、平台 jar 如何被自动注入。
- 为什么本地 wrapper 可能报
jcenter()错、怎么处理。 - 推到 Gerrit 前的代码质量门禁怎么自查。
一、先认清「打包对象」:系统特权应用
MiCarSettings 不是应用商店那种独立可分发/可卸载的 App,而是编进车机系统镜像的特权系统应用。证据(app/src/main/AndroidManifest.xml + app/Android.bp):
| 属性 | 值 | 来源 |
|---|---|---|
coreApp | true | app/src/main/AndroidManifest.xml:22 |
android:sharedUserId | android.uid.system(与 system_server 同 UID) | AndroidManifest.xml:23 |
certificate | "platform"(平台签名) | app/Android.bp:155 |
privileged | true(装在 system/priv-app/) | app/Android.bp:164 |
overrides | ["Settings"](覆盖原生 Settings) | app/Android.bp:65 |
required | privapp_whitelist_com.android.car.settings | app/Android.bp:170 |
对打包的直接约束:
- 必须用 platform 签名 签出来的 APK 才装得上(
sharedUserId=android.uid.system校验)。 - 编译期依赖特定车机平台的
framework.jar/android.car.jar,离开对应平台产物编不过。 - 量产发行随系统镜像整体打包;单独 OTA 一个 Settings APK 也需要 platform 签名。
代码层面是模块化解耦的(四层模块 + APT 框架),但「应用分发」层面与系统强绑定——这点要先建立认知。
二、构建体系全景:Gradle 与 Soong 双轨
仓库里同时存在两套构建系统,对应不同场景:
flowchart TB subgraph Gradle["① Gradle(本仓 build.gradle)—— 开发自测 / CI 发版"] G1["./gradlew :app:assembleXcdRelease<br/>等三 Flavor"] G2["产物: app/build/outputs/apk/<br/>flavor>/release/app-flavor-release.apk"] G3["platform.keystore 签名<br/>(app/build.gradle:33-38)"] end subgraph Soong["② Soong(app/Android.bp)—— 编进系统镜像"] S1["make CarSettings<br/>lunch watt / miproduct_suiren_native"] S2["产物: out/target/product/platform/<br/>system/priv-app/CarSettings/CarSettings.apk"] S3["certificate=platform<br/>(app/Android.bp:155)"] end subgraph CI["③ CI 发版(ci_build/build_rule.yaml)"] C1["assembleDcdRelease && assembleXcdRelease"] C2["→ FDS 上传<br/>→ Artifactory 制品库<br/>com/mi/car/MiCarSettings"] end G1 --> G2 --> G3 S1 --> S2 --> S3 C1 --> C2
| 路径 | 用途 | 你什么时候用 |
|---|---|---|
| ① Gradle 本地 | 出 debug/release APK 自测 | 日常开发最常用 |
| ② Soong/Make | 编进 system/priv-app,随镜像发布 | 出整机版本(工程/版本团队) |
| ③ CI 发版 | 编译 + 上 FDS + 发 Artifactory 制品库 | 正式发版(CI 触发) |
⚠️ 注意:
app/Android.bp里的源码路径写的是MiCarSettings/aosp/src/...、MiCarSettings/micar/src/...,这是 AOSP 树内同步后的目录布局。本 Gradle 仓里没有这层目录,所以app/Android.bp不能在本仓直接m/make——必须先repo sync进 AOSP 树(路径形如packages/apps/Car/MiCarSettings/)才能触发 Soong 编译。
三、环境准备 Checklist
| 项 | 要求 | 备注 |
|---|---|---|
| JDK | JDK 17(CI 用 Open_JDK_17,ci_build/build_rule.yaml:11) | sourceCompatibility 1.8(app/build.gradle:55-58)只是字节码目标,跑 Gradle 仍需 JDK 17 |
| Android SDK | compileSdk 33、buildTools 29.0.2(constants.gradle:3-5) | local.properties 配 sdk.dir |
| 平台 jar 产物 | settingsCommon/tools/out/target/common/obj/JAVA_LIBRARIES/ 下的 framework/car/wifi jar | ✅ 本仓已就位(javalib_watt_250721.jar、xcd_javalib_1129.jar 等已确认存在) |
| 小米内网 Maven | pkgs.d.xiaomi.net 可达 | 拉 AGP、kotlin、micarx.*、dfx_sdk 等内网制品 |
platform.keystore | 仓库根目录 | ✅ 已存在(2915 字节,alias platform_key,口令 android) |
project.properties | 含 versionName/versionCode | 当前 1.0.0.165-dev / 2026063001 |
快速自检:
java -version # 期望 17.x
ls settingsCommon/tools/out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/
# 期望看到 javalib_watt_250721.jar xcd_javalib_1129.jar四、路径 ①:本地 Gradle 打包(日常开发)
4.1 三 Flavor 与源码叠加
flavorDimensions "miPlatform",三套 flavor(app/build.gradle:11-25)。源码叠加规则(app/build.gradle:60-88,已逐行核实):
| Flavor | Manifest | java.srcDirs | res.srcDirs |
|---|---|---|---|
| dcd(国内 watt) | src/main/AndroidManifest.xml | main + dcddif | main + dcddif + region/cn |
| xcd(国内 suiren) | src/main/AndroidManifest.xml | main + xcddif | main + xcddif + region/cn |
| global(海外) | src/region/global/AndroidManifest.xml(独立) | main + xcddif + region/global | main + xcddif + region/global |
口诀:dcd = main + dcddif + region/cn;xcd = main + xcddif + region/cn;global = main + xcddif + region/global。 实测
app/src/dcddif、app/src/xcddif目前只有 res(java/aidl 子目录为空,Gradle 容忍空目录)。
4.2 平台 jar 自动注入(核心机制,必懂)
这是本仓构建最巧妙也最易踩坑的环节,分两步:
flowchart LR A["dependencies.gradle<br/>afterEvaluate (L1-54)"] -->|每个 Android 子模块| B["按 flavor 自动加<br/>xxxCompileOnly:<br/>framework.jar / android.car / WifiTracker"] C["build.gradle<br/>gradle.projectsEvaluated (L100-136)"] -->|扫 task 名含 assemble| D{"task 名含?"} D -->|Dcd| E["把 DCD framework.jar<br/>前置到 bootstrapClasspath"] D -->|Xcd / Global| F["把 XCD framework.jar<br/>前置到 bootstrapClasspath"] B --> G["javac 解析 android.* / @hide API<br/>优先用平台 jar 而非 SDK stub"] E --> G F --> G
- 步骤 A(
dependencies.gradle:1-54):每个 Android 子模块 evaluate 完,按 flavor 自动加dcdCompileOnly/xcdCompileOnly/globalCompileOnly(framework.jar、android.car.jar、WifiTrackerLib.jar)。注意 global 复用 xcd 的 jar(dependencies.gradle:18-19)。 - 步骤 B(
build.gradle:100-136):扫描命令行gradle.startParameter.taskNames,找含assemble的 task 名,再按contains("Dcd")/contains("Xcd")/contains("Global")把对应 framework.jar 前置到JavaCompile.bootstrapClasspath(排在 JDK rt.jar 前),让javac解析android.car.Car、SystemProperties等 hide API 时用平台版本。
📌 关键推论:flavor 靠 task 名判定。所以一次 gradle 调用只能编一个 flavor——这正是 CI 用
assembleDcdRelease && assembleXcdRelease分两次的原因(ci_build/build_rule.yaml:4)。
4.3 构建命令与产物
# === Debug(自测)===
./gradlew :app:assembleXcdDebug # → app/build/outputs/apk/xcd/debug/app-xcd-debug.apk
./gradlew :app:assembleDcdDebug # → app/build/outputs/apk/dcd/debug/app-dcd-debug.apk
./gradlew :app:assembleGlobalDebug # → app/build/outputs/apk/global/debug/app-global-debug.apk
# === Release(发版/签名包)===
./gradlew :app:assembleXcdRelease # → app/build/outputs/apk/xcd/release/app-xcd-release.apk
./gradlew :app:assembleDcdRelease # → app/build/outputs/apk/dcd/release/app-dcd-release.apk
./gradlew :app:assembleGlobalRelease # → app/build/outputs/apk/global/release/app-global-release.apk
# === 清理 ===
./gradlew clean4.4 为什么必须用 platform 签名
signingConfigs(app/build.gradle:26-39)的 debug 和 release 都指向根目录 platform.keystore(alias platform_key,口令 android)。原因:AndroidManifest.xml:23 的 android:sharedUserId="android.uid.system" 要求 APK 必须用平台 key 签名才能进 system uid 组,否则安装报 INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,或一启动就 SecurityException。
⚠️ 配置小坑(
app/build.gradle:90-102):buildTypes里写了两个同名release {}块,后者覆盖前者,最终 release 实际挂的是signingConfigs.debug。因为 debug/release 用同一 keystore,行为无差异,但语义混乱——改签名配置时要留意。
4.5 本地构建全流程
flowchart TB Start([执行 ./gradlew :app:assembleXcdRelease]) --> Chk{环境检查<br/>JDK17/平台jar/内网/keystore} Chk -->|缺失| Fix[补齐平台产物或环境] Fix --> Chk Chk -->|OK| Cfg[配置阶段: 解析 settings.gradle<br/>loadBuildConfig 读 project.properties 版本号] Cfg --> Inject1[afterEvaluate: 每个 Android 子模块<br/>按 flavor 注入 xxxCompileOnly 平台 jar] Inject1 --> Inject2[projectsEvaluated: 扫 task 名<br/>把对应 framework.jar 前置到 bootstrapClasspath] Inject2 --> Build[preBuild → compileJava → compileKotlin<br/>→ mergeResources → packageXcdRelease] Build --> Sign[用 platform.keystore 签名] Sign --> Out([产物: app/build/outputs/apk/xcd/<br/>release/app-xcd-release.apk])
五、路径 ②:编进系统镜像(Soong / Make)
5.1 Android.bp 关键字段
app/Android.bp 定义了三个 Soong 模块,真正编进 priv-app 的是 CarSettings(app/Android.bp:63-181):
| 字段 | 值 | 含义 |
|---|---|---|
name | CarSettings | 产物模块名 |
overrides | ["Settings"] | 覆盖原生 Settings |
certificate | "platform" | 平台签名 |
privileged | true | 装到 system/priv-app |
plugins | ["voice-search-annotation-processor","router-apt"] | Soong 侧接 APT(等价 Gradle 的 kapt) |
static_libs | settings-light-lib / settings-lock-lib / … | 每个对应一个 settingsPage 模块 |
dex_preopt.enabled | false | 不做 AOT,便于整包 OTA |
optimize.obfuscate | true(但被 proguard-rules.pro:13 的 -dontobfuscate 覆盖) | 实际不混淆 |
jni_libs | libffavc.hyperos / libpag.hyperos | 视频解码 + PAG 动画 |
CarSettings-core(app/Android.bp:16-61)标注//TODO 临时library 后续去除,未被 CarSettings 依赖,可忽略;CarSettingsForTesting仅供 Robolectric 单测。
5.2 两条 lunch 路径
# 国内 DCD 平台(watt)—— build.sh
. build/envsetup.sh
lunch watt-userdebug
make CarSettings
# 国内 XCD 平台(suiren)—— .cabinconf.yaml(舱舱 CI)
source build/envsetup.sh
lunch miproduct_suiren_native-userdebug
make CarSettings -j 96产物:out/target/product/<platform>/system/priv-app/CarSettings/CarSettings.apk
(project_send_fds 给出的 watt 路径:lagvm/LINUX/android/out/target/product/watt/system/priv-app/CarSettings/CarSettings.apk)
5.3 模块 → Soong lib 映射(节选)
| settingsPage 模块 | Soong static_lib |
|---|---|
| micarLightSettings | settings-light-lib |
| micarLockSettings | settings-lock-lib |
| micarDrivingSettings | settings-driving-lib |
| micarChargeSettings | dcd-settings-charge-lib(xcd 版叫 xcd-settings-charge-lib) |
| micarConnectionSettings | dcd-settings-connection-lib(xcd 版叫 xcd-settings-connection-lib) |
| VehicleBodyControl | settings-vehicle-control-lib |
| settingsAutoPilot | settings-autopilot-lib |
| base/settingsBaseUi | settings-base-ui |
| base/settingsVehicleLib | settings-vechicle-lib(注意拼写) |
📌 命名不一致坑:充电/连接/base 有 dcd/xcd 双版本,其它页面只有一份(Soong 默认走 dcd 布局)。新增模块时要同步在
app/Android.bp的static_libs注册对应的 lib。
六、路径 ③:CI 发版
6.1 ci_build/build_rule.yaml 逐字段
build_cmd: "./gradlew clean :app:assembleDcdRelease && ./gradlew :app:assembleXcdRelease"
transfer: ['./app/build/outputs/apk/dcd/release/app-dcd-release.apk',
'./app/build/outputs/apk/xcd/release/app-xcd-release.apk']
fds_path: 'MiCarSettings/apk/commit'
publish_cmd: "./project_publish_dcd_app.sh && ./project_publish_xcd_app.sh"
version_file: "./project.properties"
mvn_group: 'com/mi/car'
jdk_version: Open_JDK_17
force_push: 1 # 同版本号也强推进库
auto_sign: 0 # 不自动触发座舱量产签名- CI 只编 dcd + xcd 两个 release APK(不编 global;global 需独立跑
project_publish_global_app.sh)。 force_push: 1:版本号直接进库,不走 review。auto_sign: 0:上传到 Artifactory 的是 platform 签名 APK,量产签名由下游 OTA/产线触发(代码库内无量产密钥)。
6.2 updateApkAssembleXxxRelease 到底做什么
project_publish_*.sh 调的 updateApkAssemble{Dcd,Xcd,Global}Release 定义在 build.gradle:235-242,本身是空壳 task,唯一作用是 dependsOn: ['app:artifactoryPublish']。即名字虽叫 “updateApk”,实际是把 release APK 上传到小米内网 Maven 制品库(https://pkgs.d.xiaomi.net/artifactory),不是复制到 AOSP out。
发布坐标:groupId=com.mi.car,artifactId=MiCarSettings,version=<versionName>-<flavor>(如 1.0.0.165-dev-xcd)。
6.3 CI 全流程
sequenceDiagram participant CI as CI 系统 participant Gradle participant FDS participant Art as Artifactory 制品库 CI->>CI: 读 project.properties 版本号<br/>JDK17,build_dir=./ CI->>Gradle: assembleDcdRelease && assembleXcdRelease Gradle-->>CI: app-dcd-release.apk + app-xcd-release.apk<br/>(platform 签名) CI->>FDS: 上传两个 APK → MiCarSettings/apk/commit CI->>Gradle: project_publish_dcd/xcd_app.sh<br/>= updateApkAssemble{Dcd,Xcd}Release Gradle->>Art: artifactoryPublish<br/>com/mi/car/MiCarSettings-1.0.0.165-dev-{dcd,xcd} Note over CI: auto_sign=0<br/>座舱量产签名由下游触发
七、版本号管理
- 存储:
project.properties(versionName=1.0.0.165-dev、versionCode=2026063001)。 - 读取:
build.gradle:164-222的loadBuildConfig()。- 本地构建:直接读
project.properties。 - CI 构建:读
GLOBAL_APP_VERSION_NAME/CODE环境变量,并回写project.properties(提交 diff 留版本痕)。
- 本地构建:直接读
- 注入:
app/build.gradle:47-48写入defaultConfig,所有子模块统一读rootProject.ext,保证全仓版本一致。 - 发布后缀:制品库 version =
versionName-{dcd|xcd|global}。
八、代码质量与门禁
| 机制 | 命令/位置 | 说明 |
|---|---|---|
| Checkstyle(Java) | ./gradlew javaCheckstyle | Checkstyle 8.12,规则 settingsCommon/tools/checkstyle/checkstyle.xml(checkstyle.gradle:6-20) |
| Detekt(Kotlin) | ./gradlew detekt | 1.22.0,规则 settingsCommon/tools/checkstyle/detekt.yml(checkstyle.gradle:43-62) |
| 聚合 | ./gradlew checkStyleCode | = javaCheckstyle + detekt(checkstyle.gradle:89-93) |
| 增量检查 | -PchangedJavaFiles=... / -PchangedKotlinFiles=... | 按换行分隔的绝对路径,只校验改动文件 |
| pre-push hook | settingsCommon/tools/hooks/pre-push | push 前自动跑 javaCheckstyle + detekt,失败则阻断(app/installHook.gradle 安装) |
| PREUPLOAD.cfg | PREUPLOAD.cfg | repo 推送时跑 checkstyle/ktlint + 校验 Change-Id/Test 字段(钩子在 AOSP 树的 prebuilts/) |
推送前自查三连:
./gradlew checkStyleCode # 或增量版
git commit -s -m "[Bugfix][All-Vehicle][模块] 简述 ..."
git push origin HEAD:refs/for/dev九、踩坑 FAQ(实测核实)
下列前 4 条是仓库里真实存在的配置 bug(已逐一验证),新手极易踩中。
🐞 1. ./gradlew help 报 Could not find method jcenter()
- 根因:本仓 Gradle wrapper 是
gradle-9.0-milestone-1(gradle/wrapper/gradle-wrapper.properties:4),而build.gradle:43,92与settings.gradle里仍用了jcenter()——该方法在 Gradle 9 已移除。 - 说明:此前文档里写的「Gradle 7.2.0」其实是 AGP 版本(
build.gradle:46),不是 Gradle 发行版。 - 处理(任选):
- 降级 wrapper:
./gradlew wrapper --gradle-version 7.4.2 - 或把所有
jcenter()替换为mavenCentral()(小米内网镜像已覆盖)
- 降级 wrapper:
-
注:CI 环境能构建成功,说明 CI 侧 wrapper/环境与本仓可能不同;本地首次构建若遇此错,按上法处理。
🐞 2. ./gradlew javaCheckstyle 找不到 checkstyle.xml
- 根因:
checkstyle.gradle:8-12写死if (rootProject.name == "MiCarSettings") path = "/tools/checkstyle/",但根目录没有tools/checkstyle/(实际在settingsCommon/tools/checkstyle/)。已核实根tools/checkstyle/不存在。 - 处理:把
checkstyle.gradle:9改为path = "/settingsCommon/tools/checkstyle/",或建软链ln -s settingsCommon/tools tools。
🐞 3. preBuild 报找不到 hooks/pre-push
- 根因:
app/installHook.gradle:3用from file("hooks/pre-push")(即app/hooks/pre-push),但app/hooks/不存在(已核实)。模板实际在settingsCommon/tools/hooks/pre-push。 - 处理:把
app/installHook.gradle的源改为file("../settingsCommon/tools/hooks/pre-push"),或把模板复制到app/hooks/。
🐞 4. detekt 引用未定义常量
- 根因:
checkstyle.gradle:60引用了DEFAULT_SRC_DIR_JAVA等四个未定义变量(只有DEFAULT_MAIN_SRC_DIR在 L4 定义)。 - 处理:始终走增量
./gradlew detekt -PchangedKotlinFiles=...,或把 L60 改为input = files(DEFAULT_MAIN_SRC_DIR)。
5. 缺平台 jar → cannot find symbol class Car
- 平台 jar 已就位(
framework_intermediates/下两 jar 存在)。若升级平台后文件名变了(如javalib_watt_0901.jar),需同步改constants.gradle:19-20,32-33,40-41的常量。
6. 签名不对 → INSTALL_FAILED_SHARED_USER_INCOMPATIBLE
- 别用默认 debug.keystore;必须用根目录
platform.keystore(sharedUserId=android.uid.system强制)。
7. JDK 版本不对 → Unsupported class file major version 61
- 本地
JAVA_HOME指 JDK 17。
8. Gradle daemon OOM
gradle.properties:24仅-Xmx4096m;大型构建可加-XX:MaxMetaspaceSize=1g,必要时提到 8G。
十、快速上手:我要打个 XCD debug 包
cd /home/zbc/car/MiCarSettings
# 1. 自检(JDK17 + 平台 jar)
java -version
ls settingsCommon/tools/out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/
# 2.(首次)若 gradlew help 报 jcenter() 错,先处理(见坑 1)
# 3. 打包
./gradlew :app:assembleXcdDebug
# 4. 拿产物
ls app/build/outputs/apk/xcd/debug/
# → app-xcd-debug.apk(platform 签名)
# 5. 装到车机(需 platform 签名才能覆盖 priv-app)
adb install -r app/build/outputs/apk/xcd/debug/app-xcd-debug.apk十一、命令速查表
# 本地构建
./gradlew :app:assembleXcdDebug | assembleDcdDebug | assembleGlobalDebug
./gradlew :app:assembleXcdRelease | assembleDcdRelease | assembleGlobalRelease
./gradlew clean
# 代码质量
./gradlew checkStyleCode # javaCheckstyle + detekt
./gradlew javaCheckstyle -PchangedJavaFiles="$(git diff --name-only -- '*.java')"
./gradlew detekt -PchangedKotlinFiles="$(git diff --name-only -- '*.kt')"
# 单测
./gradlew :base:settingsBaseUi:testXcdDebugUnitTest
# 发布到内网 Maven 制品库
./project_publish_dcd_app.sh | project_publish_xcd_app.sh | project_publish_global_app.sh
# 编进系统镜像(需在 AOSP 树内)
. build/envsetup.sh && lunch watt-userdebug && make CarSettings # dcd
. build/envsetup.sh && lunch miproduct_suiren_native-userdebug && make CarSettings -j 96 # xcd
# 提交到 Gerrit
git commit -s -m "[类型][All-Vehicle][模块] 简述 ..."
git push origin HEAD:refs/for/dev十二、关键文件索引
| 用途 | 路径 |
|---|---|
| 根构建脚本 | build.gradle |
| 子模块注册 | settings.gradle |
| Gradle/JVM 参数 | gradle.properties |
| wrapper 版本 | gradle/wrapper/gradle-wrapper.properties:4(=gradle-9.0-milestone-1) |
| 平台 jar 路径字典 | constants.gradle |
| 平台 jar 自动注入 | dependencies.gradle(afterEvaluate)+ build.gradle:100-136(bootstrapClasspath) |
| checkstyle/detekt | checkstyle.gradle + settingsCommon/tools/checkstyle/ |
| app 构建配置 | app/build.gradle |
| 签名密钥 | platform.keystore |
| 版本号 | project.properties |
| CI 规则 | ci_build/build_rule.yaml |
| 发布脚本 | project_publish_{dcd,xcd,global}_app.sh |
| Soong 构建 | app/Android.bp |
| AOSP 整编 | build.sh、.cabinconf.yaml |
| pre-push 模板 | settingsCommon/tools/hooks/pre-push |
📌 新手记住
- 日常只走路径 ①:
./gradlew :app:assembleXcdDebug出 APK 自测;编进镜像和 CI 发版是工程/版本团队的事。 - 一次只编一个 flavor:平台 jar 靠 task 名注入,别想
assembleDcdRelease assembleXcdRelease一次双编。 - 签名必须 platform:
platform.keystore(根目录),否则装不上 priv-app。 - 本地首次构建可能踩 4 个配置 bug(jcenter/checkstyle 路径/hooks/detekt 常量),见第九节逐一处理。
7.2.0是 AGP 不是 Gradle:实际 wrapper 是gradle-9.0-milestone-1,遇到jcenter()报错别意外。- 推送前
./gradlew checkStyleCode:Gerrit/pre-push 会强制 detekt,本地先过省得被打回。