MiCarSettings 打包教程

本篇是「照着做就能打出包」的实操教程。覆盖 本地 Gradle 打包编进系统镜像(Soong)CI 发版 三条链路,含完整命令、产物路径、流程图与踩坑 FAQ。 所有结论均来自真实脚本/源码阅读,引用格式为 文件路径:行号


〇、读完你能掌握什么

  1. 这个 App 的「身份」——为什么它不是普通可卸载应用,打包时要受什么约束。
  2. 三条打包路径分别在什么时候用、命令是什么、产物在哪。
  3. 三 Flavor(dcd / xcd / global)怎么叠加源码、平台 jar 如何被自动注入。
  4. 为什么本地 wrapper 可能报 jcenter() 错、怎么处理。
  5. 推到 Gerrit 前的代码质量门禁怎么自查。

一、先认清「打包对象」:系统特权应用

MiCarSettings 不是应用商店那种独立可分发/可卸载的 App,而是编进车机系统镜像的特权系统应用。证据(app/src/main/AndroidManifest.xml + app/Android.bp):

属性来源
coreApptrueapp/src/main/AndroidManifest.xml:22
android:sharedUserIdandroid.uid.system(与 system_server 同 UID)AndroidManifest.xml:23
certificate"platform"(平台签名)app/Android.bp:155
privilegedtrue(装在 system/priv-app/app/Android.bp:164
overrides["Settings"](覆盖原生 Settings)app/Android.bp:65
requiredprivapp_whitelist_com.android.car.settingsapp/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

要求备注
JDKJDK 17(CI 用 Open_JDK_17ci_build/build_rule.yaml:11sourceCompatibility 1.8app/build.gradle:55-58)只是字节码目标,跑 Gradle 仍需 JDK 17
Android SDKcompileSdk 33、buildTools 29.0.2(constants.gradle:3-5local.propertiessdk.dir
平台 jar 产物settingsCommon/tools/out/target/common/obj/JAVA_LIBRARIES/ 下的 framework/car/wifi jar本仓已就位javalib_watt_250721.jarxcd_javalib_1129.jar 等已确认存在)
小米内网 Mavenpkgs.d.xiaomi.net 可达拉 AGP、kotlin、micarx.*、dfx_sdk 等内网制品
platform.keystore仓库根目录✅ 已存在(2915 字节,alias platform_key,口令 android
project.propertiesversionName/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,已逐行核实):

FlavorManifestjava.srcDirsres.srcDirs
dcd(国内 watt)src/main/AndroidManifest.xmlmain + dcddifmain + dcddif + region/cn
xcd(国内 suiren)src/main/AndroidManifest.xmlmain + xcddifmain + xcddif + region/cn
global(海外)src/region/global/AndroidManifest.xml(独立)main + xcddif + region/globalmain + xcddif + region/global

口诀:dcd = main + dcddif + region/cnxcd = main + xcddif + region/cnglobal = main + xcddif + region/global。 实测 app/src/dcddifapp/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
  • 步骤 Adependencies.gradle:1-54):每个 Android 子模块 evaluate 完,按 flavor 自动加 dcdCompileOnly/xcdCompileOnly/globalCompileOnly(framework.jar、android.car.jar、WifiTrackerLib.jar)。注意 global 复用 xcd 的 jardependencies.gradle:18-19)。
  • 步骤 Bbuild.gradle:100-136):扫描命令行 gradle.startParameter.taskNames,找含 assemble 的 task 名,再按 contains("Dcd") / contains("Xcd") / contains("Global") 把对应 framework.jar 前置到 JavaCompile.bootstrapClasspath(排在 JDK rt.jar 前),让 javac 解析 android.car.CarSystemProperties 等 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 clean

4.4 为什么必须用 platform 签名

signingConfigsapp/build.gradle:26-39)的 debug 和 release 都指向根目录 platform.keystore(alias platform_key,口令 android)。原因:AndroidManifest.xml:23android: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 的是 CarSettingsapp/Android.bp:63-181):

字段含义
nameCarSettings产物模块名
overrides["Settings"]覆盖原生 Settings
certificate"platform"平台签名
privilegedtrue装到 system/priv-app
plugins["voice-search-annotation-processor","router-apt"]Soong 侧接 APT(等价 Gradle 的 kapt)
static_libssettings-light-lib / settings-lock-lib / …每个对应一个 settingsPage 模块
dex_preopt.enabledfalse不做 AOT,便于整包 OTA
optimize.obfuscatetrue(但被 proguard-rules.pro:13-dontobfuscate 覆盖)实际不混淆
jni_libslibffavc.hyperos / libpag.hyperos视频解码 + PAG 动画

CarSettings-coreapp/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.apkproject_send_fds 给出的 watt 路径:lagvm/LINUX/android/out/target/product/watt/system/priv-app/CarSettings/CarSettings.apk

5.3 模块 → Soong lib 映射(节选)

settingsPage 模块Soong static_lib
micarLightSettingssettings-light-lib
micarLockSettingssettings-lock-lib
micarDrivingSettingssettings-driving-lib
micarChargeSettingsdcd-settings-charge-lib(xcd 版叫 xcd-settings-charge-lib
micarConnectionSettingsdcd-settings-connection-lib(xcd 版叫 xcd-settings-connection-lib
VehicleBodyControlsettings-vehicle-control-lib
settingsAutoPilotsettings-autopilot-lib
base/settingsBaseUisettings-base-ui
base/settingsVehicleLibsettings-vechicle-lib(注意拼写)

📌 命名不一致坑:充电/连接/base 有 dcd/xcd 双版本,其它页面只有一份(Soong 默认走 dcd 布局)。新增模块时要同步在 app/Android.bpstatic_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.propertiesversionName=1.0.0.165-devversionCode=2026063001)。
  • 读取:build.gradle:164-222loadBuildConfig()
    • 本地构建:直接读 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 javaCheckstyleCheckstyle 8.12,规则 settingsCommon/tools/checkstyle/checkstyle.xmlcheckstyle.gradle:6-20
Detekt(Kotlin)./gradlew detekt1.22.0,规则 settingsCommon/tools/checkstyle/detekt.ymlcheckstyle.gradle:43-62
聚合./gradlew checkStyleCode= javaCheckstyle + detekt(checkstyle.gradle:89-93
增量检查-PchangedJavaFiles=... / -PchangedKotlinFiles=...按换行分隔的绝对路径,只校验改动文件
pre-push hooksettingsCommon/tools/hooks/pre-pushpush 前自动跑 javaCheckstyle + detekt,失败则阻断(app/installHook.gradle 安装)
PREUPLOAD.cfgPREUPLOAD.cfgrepo 推送时跑 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 helpCould not find method jcenter()

  • 根因:本仓 Gradle wrapper 是 gradle-9.0-milestone-1gradle/wrapper/gradle-wrapper.properties:4),而 build.gradle:43,92settings.gradle 里仍用了 jcenter()——该方法在 Gradle 9 已移除。
  • 说明:此前文档里写的「Gradle 7.2.0」其实是 AGP 版本(build.gradle:46),不是 Gradle 发行版。
  • 处理(任选):
    • 降级 wrapper:./gradlew wrapper --gradle-version 7.4.2
    • 或把所有 jcenter() 替换为 mavenCentral()(小米内网镜像已覆盖)
  • 注: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:3from 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.keystoresharedUserId=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/detektcheckstyle.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

📌 新手记住

  1. 日常只走路径 ①./gradlew :app:assembleXcdDebug 出 APK 自测;编进镜像和 CI 发版是工程/版本团队的事。
  2. 一次只编一个 flavor:平台 jar 靠 task 名注入,别想 assembleDcdRelease assembleXcdRelease 一次双编。
  3. 签名必须 platformplatform.keystore(根目录),否则装不上 priv-app。
  4. 本地首次构建可能踩 4 个配置 bug(jcenter/checkstyle 路径/hooks/detekt 常量),见第九节逐一处理。
  5. 7.2.0 是 AGP 不是 Gradle:实际 wrapper 是 gradle-9.0-milestone-1,遇到 jcenter() 报错别意外。
  6. 推送前 ./gradlew checkStyleCode:Gerrit/pre-push 会强制 detekt,本地先过省得被打回。