# TestAar **Repository Path**: developer_wind/TestAar ## Basic Information - **Project Name**: TestAar - **Description**: aar打包,热修复 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-06-02 - **Last Updated**: 2026-06-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Android app 项目:模块打包 AAR 教程 本文档基于当前 **TestAar** 工程的实际配置编写,说明如何新增/维护 **zcclib**(Android Library)模块、如何在 **app** 中引用,以及如何生成并在其他工程中使用 **AAR** 文件。 > **命名说明**:本仓库模块名为 `zcclib`(包名 `com.example.zcclib`)。若你习惯称为 zzclib,仅名称不同,步骤一致。 > **热修复集成**:将 AAR 打进 Tinker 宿主时的版本关系见 [第七章](#七热修复场景aar-与宿主工程的版本对应关系)。 > **打包流程**:将 zcclib 打成 `.aar` 的步骤见 [第十一章](#十一打包-aar-流程)。 > **热修宿主流程**:AAR 接入 Tinker 宿主 App 的操作见 [第十二章](#十二热修复场景宿主-app-流程)。 --- ## 一、项目结构概览 ``` TestAar/ ├── app/ # 宿主 Application 模块(调试/验证 AAR) ├── zcclib/ # Android Library 模块(产出 AAR) ├── gradle/ │ └── libs.versions.toml # 统一依赖与插件版本 ├── settings.gradle.kts # 注册子模块 ├── build.gradle.kts # 根工程插件 └── gradle.properties # AndroidX 等全局配置 ``` | 模块 | 类型 | 包名 / applicationId | 作用 | |------|------|----------------------|------| | `app` | Application | `com.example.testaar` | 演示如何调用 zcclib 中的 Activity | | `zcclib` | Library | `com.example.zcclib` | 封装可复用 UI/逻辑,打包为 AAR | --- ## 二、新增 zcclib 类 Library 模块 ### 方式 A:Android Studio(推荐) 1. 菜单 **File → New → New Module…** 2. 选择 **Android Library**,Next 3. 填写: - **Module name**:`zcclib` - **Package name**:`com.example.zcclib`(与 `namespace` 保持一致) - **Language**:Java(与本项目一致) - **Minimum SDK**:库的值应 **≤** app(本库 21,app 23;规则见 [4.5](#45-minimum-sdk-怎么配)) 4. Finish 后,Studio 会自动在 `settings.gradle.kts` 中加入 `include(":zcclib")`。 ### 方式 B:手动创建目录 1. 复制本仓库 `zcclib/` 目录结构,或新建: ``` zcclib/ ├── build.gradle ├── proguard-rules.pro └── src/main/ ├── AndroidManifest.xml ├── java/com/example/zcclib/ └── res/ ``` 2. 在 `settings.gradle.kts` 末尾添加: ```kotlin include(":zcclib") ``` --- ## 三、zcclib 模块配置详解 ### 3.1 `zcclib/build.gradle` 本模块使用 **Groovy** 脚本,核心是应用 **`com.android.library`** 插件(不是 `com.android.application`): ```gradle plugins { id 'com.android.library' } android { // AGP 8.x 必填:与代码包名、R 类命名空间一致 namespace 'com.example.zcclib' compileSdk 34 defaultConfig { minSdk 21 targetSdk 36 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false // 库模块不要对库本身做 shrinkResources,避免宿主合并资源异常 shrinkResources false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 } } dependencies { implementation libs.appcompat implementation libs.material implementation libs.activity implementation libs.constraintlayout // ... } ``` **要点:** | 配置项 | 说明 | |--------|------| | `com.android.library` | 声明为库模块,Gradle 会执行 `bundleReleaseAar` 等任务产出 AAR | | `namespace` | 必须配置;与 `AndroidManifest` 中组件类名、Java 包名一致 | | `shrinkResources false` | Library 的 release 不要压缩资源 | | `implementation` 依赖 | **不会**打进 AAR;使用方 app 需自行添加相同依赖(见下文「依赖传递」) | ### 3.2 版本目录 `gradle/libs.versions.toml` 根工程通过 Version Catalog 管理依赖。库模块与 app 共用同一套 `libs.*`: ```toml [plugins] android-application = { id = "com.android.application", version.ref = "agp" } android-library = { id = "com.android.library", version.ref = "agp" } ``` **建议(可选)**:在根 `build.gradle.kts` 中同时声明 library 插件,便于子模块统一版本: ```kotlin plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.android.library) apply false } ``` 当前 `zcclib/build.gradle` 直接写 `id 'com.android.library'` 也可正常构建;若遇插件版本解析问题,可改为在 `zcclib` 使用 `alias(libs.plugins.android.library)`(需将 `build.gradle` 改为 `build.gradle.kts`)。 ### 3.3 `zcclib/src/main/AndroidManifest.xml` 库模块的 Manifest 会 **合并** 到宿主 app。本示例注册了权限与 Activity: ```xml ``` **发布 AAR 时的建议:** - 库内 Activity 建议设置 `android:exported="true"`(Android 12+ 要求显式声明),由宿主通过显式 `Intent` 跳转。 - **不要**在对外发布的 AAR 里保留 ` MAIN / LAUNCHER`(本仓库若用于单独调试库可保留;交付第三方时应删掉,避免宿主出现双启动图标)。 - 库 Manifest 中的 `` 属性会与宿主合并,注意 `android:icon`、`android:label` 是否与宿主冲突。 ### 3.4 对外 API 示例(Java) 库内 Activity 提供静态 `start` 方法,供 app 调用: ```java // com.example.zcclib.SecondActivity public static void start(Context context) { Intent intent = new Intent(context, SecondActivity.class); context.startActivity(intent); } ``` 资源、布局放在 `zcclib/src/main/res/`,R 类为 `com.example.zcclib.R`。 --- ## 四、app 模块如何配置 ### 4.1 注册模块 `settings.gradle.kts` ```kotlin rootProject.name = "TestAar" include(":app") include(":zcclib") ``` ### 4.2 开发阶段:工程依赖(当前方式) `app/build.gradle.kts`: ```kotlin dependencies { implementation(libs.appcompat) implementation(libs.material) implementation(libs.activity) implementation(libs.constraintlayout) // 直接依赖同工程的 library 模块,改代码可即时编译 implementation(project(":zcclib")) } ``` `app` 中调用示例(`MainActivity.java`): ```java import com.example.zcclib.SecondActivity; SecondActivity.start(MainActivity.this); ``` ### 4.3 集成阶段:使用已生成的 AAR 文件 1. 将 AAR 复制到宿主工程,例如:`app/libs/zcclib-release.aar` 2. 修改 `app/build.gradle.kts`: ```kotlin dependencies { // 方式 1:单文件 implementation(files("libs/zcclib-release.aar")) // 方式 2:libs 目录下所有 aar(需配合 repositories) // implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar")))) } ``` 3. **必须**在宿主 app 中补充 zcclib 使用到的 **implementation** 依赖(AAR 不包含这些 jar/aar): ```kotlin implementation(libs.appcompat) implementation(libs.material) implementation(libs.activity) implementation(libs.constraintlayout) ``` 4. 若 zcclib 的 `AndroidManifest` 中有权限、Activity、Service 等,合并后宿主 **无需** 再手写一遍(除非要覆盖 `exported` 等属性)。 5. 宿主与库的 **minSdk 关系**见 [4.5 Minimum SDK 怎么配](#45-minimum-sdk-怎么配)。 ### 4.4 app 与 zcclib 配置对照 | 项 | app | zcclib | |----|-----|--------| | 插件 | `com.android.application` | `com.android.library` | | namespace | `com.example.testaar` | `com.example.zcclib` | | applicationId | 有 `com.example.testaar` | 无 | | minSdk | **23** | **21** | | compileSdk | 34 | 34 | | Java | 11 | 11 | ### 4.5 Minimum SDK 怎么配 两个模块各自在 `defaultConfig` 里配置 `minSdk`(本工程:`zcclib` 为 21,`app` 为 23)。合并规则可以记成一句话: **zcclib 的 minSdk 必须 ≤ app 的 minSdk**( `库的要求不能高于宿主对外承诺的最低系统版本`)。 等价写法(任选一种记即可): | 写法 | 含义 | |------|------| | **zcclib minSdk ≤ app minSdk** | 库的最低版本 **低于或等于** 宿主 | | **app minSdk ≥ zcclib minSdk** | 宿主的最低版本 **高于或等于** 库 | **不能**写成「app 必须低于 zcclib」——那样会把关系说反。 #### 为什么 - **app 的 minSdk**:最终 APK 声明「从 Android 几开始能安装、能跑」。 - **zcclib 的 minSdk**:库在开发和打包时声明「库代码/依赖至少依赖到哪一级 API」。 - 宿主集成库后,真正安装到用户手机上的下限由 **app** 决定。若 app 比库还低(例如 app=21、库=26),会出现:商店/清单允许 API 21 设备安装,但库在 21~25 上可能因调用了 API 26+ 的接口而崩溃。 #### 本工程示例(正确) ``` zcclib minSdk = 21 ← 较低(库可覆盖更广) app minSdk = 23 ← 较高(宿主实际只支持 23+) ``` 21 ≤ 23,配置合法。 #### 反例(错误) ``` zcclib minSdk = 26 app minSdk = 21 ← 宿主低于库,不推荐/可能运行异常 ``` 26 > 21,违反「库 ≤ 宿主」。应 **提高 app 的 minSdk 到至少 26**,或 **降低 zcclib 的 minSdk**(并确认库内未使用更高 API)。 #### 可以相等吗? 可以。例如两者都设为 `23` 完全没问题;此时 **zcclib minSdk = app minSdk** 仍满足「库 ≤ 宿主」。 #### 配置时怎么选 | 角色 | 建议 | |------|------| | **zcclib** | 设为库代码真正需要的最低 API(能低则低,方便更多宿主复用) | | **app** | 设为产品要支持的最低 API,且 **≥ zcclib 的 minSdk** | --- ## 五、打包生成 AAR ### 5.1 命令行(Windows PowerShell) 在项目根目录 `TestAar` 下执行: ```powershell cd D:\AsWorkSpace\TestAar .\gradlew.bat :zcclib:assembleRelease ``` 仅打 Debug 包: ```powershell .\gradlew.bat :zcclib:assembleDebug ``` 查看可用任务: ```powershell .\gradlew.bat :zcclib:tasks --group=build ``` ### 5.2 Android Studio 1. 右侧 **Gradle** 面板 → **TestAar → zcclib → Tasks → build** 2. 双击 **`assembleRelease`**(或 **`bundleReleaseAar`**) ### 5.3 产物路径 | 构建类型 | AAR 路径 | |----------|----------| | Release | `zcclib/build/outputs/aar/zcclib-release.aar` | | Debug | `zcclib/build/outputs/aar/zcclib-debug.aar` | 构建成功后,可将 `zcclib-release.aar` 拷贝到其他工程 `libs/` 目录分发。 ### 5.4 清理后重新打包 ```powershell .\gradlew.bat :zcclib:clean :zcclib:assembleRelease ``` --- ## 六、验证流程 ### 6.1 本仓库内验证(module 依赖) ```powershell .\gradlew.bat :app:assembleDebug ``` 安装 app 后,点击「点击跳转aar 主页面」应能打开 zcclib 中的 `SecondActivity`。 ### 6.2 外部工程验证(AAR 文件) 1. 复制 `zcclib-release.aar` 到目标工程 `app/libs/` 2. 按 [4.3](#43-集成阶段使用已生成的-aar-文件) 配置依赖与 AndroidX 库 3. 调用 `SecondActivity.start(context)` 或 `MainActivity.start(context)` 4. 若闪退,检查 logcat:常见原因为缺少 `appcompat/material` 依赖,或 Activity 未 `exported`、Manifest 未合并 --- ## 七、热修复场景:AAR 与宿主工程的版本对应关系 将 `zcclib-release.aar` 集成到 **Tinker 热修复宿主**(本仓库旁路工程 **[TinkerDemo](https://gitee.com/developer_wind/TinkerClaude)**,`app/libs/zcclib-release.aar`)时,除了 [minSdk](#45-minimum-sdk-怎么配) 外,还要区分好几套「版本」——它们 **不是同一个字段**,也不能用 AAR 的 `versionName` 去和 `TINKER_ID` 划等号。 更完整的热修流程见:[Android Tinker 热修复集成与使用指南 1.9.15.2](https://blog.csdn.net/xzytl60937234/article/details/161458824) ### 7.1 先分清三套「版本」 | 名称 | 配置位置 | 作用 | |------|----------|------| | **AAR / 库版本** | `zcclib/build.gradle` 的 `versionCode`、`versionName` | 写入 AAR 元数据,便于人工区分 SDK 第几版;**Tinker 不拿它做补丁校验** | | **宿主 APK 版本** | 热修复工程 `app/build.gradle` 的 `versionCode`、`versionName` | 应用商店/用户看到的版本;打 **补丁** 时通常 **不改** | | **热修基准标识** | `TINKER_ID`(及 `tinkerPatch.buildConfig.tinkerId`) | 标识「哪一版基准 APK」;**打补丁时必须与 `patch/base/` 里基准包一致** | 另外还有 **补丁包自己的说明字段**(如 TinkerDemo 里 `packageConfig.configField("patchVersion", "1.0.0")`),只用于 App 内展示或上报,**不参与**「能否合成补丁」的硬性校验。 ### 7.2 TestAar(产 AAR)与 TinkerDemo(热修宿主)当前对照 | 配置项 | zcclib(打 AAR) | TestAar `app` | TinkerDemo `app`(宿主) | 对应关系 / 建议 | |--------|------------------|---------------|----------------------------|-----------------| | **minSdk** | 21 | 23 | 21 | **zcclib ≤ 宿主**(21 ≤ 21 ✅) | | **compileSdk** | 34 | 34 | 34 | **建议一致**;宿主 ≥ 库亦可,但热修资源时差异过大易出 R 表问题 | | **targetSdk** | 36 | 36 | 34 | 由 **宿主 APK** 决定系统行为;库内 `targetSdk` 多为编译提示,热修时 **不必强行与库相同**,但库若用到高版本 API 需保证宿主 `minSdk/compileSdk` 支持 | | **Java** | 11 | 11 | 11 | **必须一致**(bytecode 级别) | | **namespace / 包名** | `com.example.zcclib` | `com.example.testaar` | `com.example.tinkerdemo` | 库包名 **打补丁前后不要改**;与宿主包名 **独立**,互不覆盖 | | **AGP(构建机)** | 8.6.0 | 8.6.0 | 8.2.2 | AAR 已编译进 dex;宿主 AGP 可略低,但 **打资源热修** 时应用 **同一套 AGP 流水线** 更稳 | | **Gradle** | 8.7 | 8.7 | 8.7 | 建议一致 | | **AndroidX appcompat** | 1.6.1 | 1.6.1 | 1.6.1 | 宿主 **≥ 库编译时版本**(见下节) | | **Material** | 1.11.0 | 1.11.0 | 1.11.0 | 同上 | | **constraintlayout** | 2.2.1 | 2.2.1 | 2.1.4 | 宿主宜 **不低于** 库所用大版本;小版本差异一般可接受 | | **库 versionCode / Name** | 1 / `"1.0"` | — | — | 仅标识 SDK;热修 AAR 时可递增便于记录,**不影响 Tinker 合成** | | **宿主 versionCode / Name** | — | 1 / `"1.0"` | 1 / `"1.0.0"` | **打补丁(不新发基准)**:通常 **不改**;发 **新基准包** 时再升 | | **TINKER_ID** | — | — | `base-1.0.0` | **打补丁时必须与基准包构建时相同**;换基准才改 | ### 7.3 各配置项在热修里的规则(除 minSdk 外) #### compileSdk - **建议**:打 AAR 的 `zcclib` 与热修复宿主的 `compileSdk` **相同**(当前均为 34)。 - **原因**:资源热修依赖稳定的 `R.txt` / `stableIds.txt`;两边 SDK 差太多时,资源 ID 分配策略可能变化,导致 `applyResourceMapping` 失效或补丁异常。 - **关系**:**宿主 compileSdk ≥ zcclib compileSdk** 一般可编译通过;热修场景仍推荐 **相等**。 #### targetSdk - **最终生效的是宿主 APK 的 `targetSdk`**,不是 AAR 里的值。 - **关系**:无「库必须低于宿主」的硬性 Gradle 校验;但若库代码按 `targetSdk 36` 写了新行为,而宿主仍是 34,需在真机验证权限/后台等差异。 - **打补丁时**:一般 **不改** 宿主 `targetSdk`;若修改,评估是否应 **发新基准包**。 #### Java(sourceCompatibility / targetCompatibility) - **必须一致**(如均为 11)。AAR 内已是 class 字节码,宿主用更高 JVM 通常可读,用 **更低** 可能无法加载。 #### namespace、类名、AAR 文件名 | 项 | 发基准(首次接入) | 打补丁(不新发基准,已上线) | |----|-------------------|----------------------| | `namespace` / Java 包名 | 确定后写入基准 APK | **不要改**(否则 dex 差量相当于删旧类加新类,风险大) | | Activity / 类全限定名 | 写入合并后的 Manifest | **不要改名**;改逻辑、改 layout 可以 | | `libs/zcclib-release.aar` 文件名 | 固定 | **保持同名覆盖**,不要换路径或 `implementation` 写法 | | 宿主 `applicationId` | 固定 | **不要改** | #### zcclib 的 versionCode / versionName - 写在 `zcclib/defaultConfig` 里,随 AAR 发布,例如 `versionName "1.0"` → 下次热修可改为 `"1.0.1"` **仅作文档/排查**。 - **与宿主 `versionCode` 无对应关系**;也 **不等于** `TINKER_ID`。 - **打补丁时**:可改,但 **不必改**;改了也不会自动让用户必须下新基准包。 #### 宿主 versionCode / versionName - **打补丁**(`tinkerPatchRelease`,用户不装新 APK):宿主 `versionCode` 保持与基准包一致(TinkerDemo 示例均为 `versionCode 1`)。 - **发新基准**(重新 `assembleRelease` 并更新 `patch/base/`):按发版节奏递增 `versionCode` / `versionName`,并 **同步修改 `TINKER_ID`**。 #### TINKER_ID(热修最关键) ```gradle // TinkerClaude/app/build.gradle def TINKER_ID = "base-1.0.0" manifestPlaceholders = [TINKER_ID: TINKER_ID] tinkerPatch { buildConfig { tinkerId = TINKER_ID } } ``` | 场景 | TINKER_ID | 基准目录 `patch/base/` | |------|-----------|-------------------------| | 首次发版(含某版 AAR) | 定一个值,如 `base-1.0.0` | 保存本次 `app-release.apk`、`mapping.txt`、`R.txt` | | **打补丁(含改 AAR / 改宿主代码)** | **保持不变** | **不要覆盖** | | 新渠道/大改版/换签名/改 applicationId | **必须换新** | 用新 APK 整包替换 | #### AndroidX 等「未打进 AAR」的依赖 - AAR **不含** `appcompat`、`material` 等;宿主必须自行 `implementation`,且版本 **建议 ≥ 打 AAR 时使用的版本**。 - 热修时若 **升级** 宿主里 Material/AppCompat 大版本,可能改变合并后的资源与 dex,**优先发新基准**;打补丁时宿主依赖版本 **尽量与打基准时一致**。 #### 混淆(R8 / ProGuard) | 模块 | TestAar zcclib | TinkerDemo 宿主 | |------|----------------|-------------------| | Release 混淆 | `minifyEnabled false`(AAR 内未混淆) | `minifyEnabled true`(混淆发生在 **宿主打 APK**) | - 打补丁时使用基准包目录下的 **`mapping.txt`**,保证 dex 差量与混淆后类名一致。 - **打补丁时**:不要换 ProGuard 规则大改宿主混淆策略;AAR 与宿主业务类会随同一套 `mapping.txt` 一起做 dex 差量。 #### 签名 - 基准 APK 与 `tinkerPatchRelease` 产出的补丁包需 **同一套 keystore**(TinkerDemo 使用 `keystore/tinker_demo.jks`)。 - 与 AAR `versionName` **无关**。 ### 7.4 两种场景对照(不要混用) ```mermaid flowchart LR subgraph base [发基准 / 首次接入 AAR] A1[TestAar 打出 zcclib-release.aar] --> A2[拷贝到 TinkerDemo app/libs] A2 --> A3[assembleRelease] A3 --> A4[保存 patch/base 三件套] A4 --> A5[用户安装基准 APK] end subgraph patch [打补丁 不新发基准] B1[改 AAR 和/或 宿主代码资源] --> B2[tinkerPatchRelease] B2 --> B3[TINKER_ID 不变] B3 --> B4[推补丁 + 冷启动] end ``` | 步骤 | 发基准(含某版 AAR) | 打补丁(不新发基准包) | |------|---------------------|------------------------| | 可改内容 | 宿主 + AAR 任意初始化 | **宿主 Java/Kotlin、res、assets** 与 **libs 下 AAR** 均可改(见下节说明) | | 更新 `zcclib-release.aar` | 放入 `app/libs/` | 若 SDK 有变更则 **覆盖**同名文件;未改 SDK 可不换 | | 宿主 `versionCode` / `TINKER_ID` | 设定初始值 | **不改** | | `patch/base/app-release.apk` | **生成并保存** | **不要动** | | `mapping.txt` / `R.txt` | **从本次 Release 保存** | **继续用基准那套** | | 构建命令 | `assembleRelease` | `tinkerPatchRelease` | | 用户侧 | 安装新 APK | 安装补丁包,冷启动 | > **措辞说明(避免误解)** > 旧称「仅热修 AAR」容易理解成 **只能改 AAR、不能改宿主 App**——这是 **错误的**。 > 正确含义是:**用户不重新安装整包 APK**,通过 Tinker **补丁** 下发变更;补丁里可以同时包含 **AAR 内的 dex/资源** 与 **宿主模块自己的代码/资源** 差量。 > 限制在于 **不能动基准契约**(`TINKER_ID`、`patch/base` 三件套、未在基准 Manifest 注册的组件等),而不是「宿主不能改」。 ### 7.5 何时必须发新基准(不能单靠打补丁) 出现以下情况时,应 **提高宿主 versionCode、修改 TINKER_ID、重新 assembleRelease 并更新 patch/base**,而不是继续 `tinkerPatchRelease`: - 修改了 `applicationId`、`TINKER_ID`、签名证书 - 新增/删除 **Manifest 组件**(如多注册一个 Activity,且基准 APK 里从未声明) - 修改了 `namespace` 或大量类名/包名重构 - 宿主 **大版本升级** compileSdk / AGP / AndroidX,导致 `R.txt` 与基准无法对齐 - 需要修改 **Tinker loader 白名单**(`SampleApplication` 等) 以下一般 **可以走打补丁**(不必让用户重装新 APK): - 改 `com.example.zcclib` 包内 Java / layout / strings(换新版 AAR 或覆盖 `libs`) - **同时**改宿主 `MainActivity`、宿主 `strings.xml`、布局等(Tinker 会对整包做 dex/res 差量) - AAR 内 `versionName` 从 `1.0` → `1.0.1` - 不改 `applicationId`、不改 `TINKER_ID`、不破坏 `patch/base` 与基准 Manifest 契约 ### 7.6 推荐版本管理习惯 1. **AAR**:`zcclib` 的 `versionName` 与 Git tag 对齐(如 `1.0.1`),每次对外发包递增。 2. **宿主**:`versionName` 表示 App 发版;**打补丁不发新基准时不升 versionCode**。 3. **热修**:用 `TINKER_ID` 绑定「哪次基准」;`patchVersion` 仅表示补丁序号。 4. 在 README 或变更记录中写清:**「基准 APK 内置 AAR 版本」→「补丁内置 AAR 版本」**,避免同事用错 `patch/base`。 --- ## 八、常见问题 ### Q1:AAR 里是否包含 AppCompat、Material? **不包含。** `implementation` 依赖在打包 AAR 时不会嵌入。宿主必须自行 `implementation` 相同(或兼容)版本的 AndroidX 库。 若希望减少宿主配置,可考虑将库中部分依赖改为 `api`(会暴露给宿主),但会增加依赖传递与版本冲突风险,需审慎使用。 ### Q2:R 类或资源找不到? - 确认 `namespace` 与 Java 包名一致。 - 宿主开启 `android.nonTransitiveRClass=true`(本工程已开启)时,库资源应通过库的 R:`com.example.zcclib.R` 访问,不要与宿主 `com.example.testaar.R` 混用。 ### Q3:Release 需要混淆吗? - 打 AAR:`zcclib` 当前 `minifyEnabled false`,生成的是未混淆的 class。 - 若库对外发布且需 ProGuard 规则,在 `zcclib/proguard-rules.pro` 中编写,并随 AAR 发布 `consumer-rules.pro`(可在 `build.gradle` 中配置 `consumerProguardFiles`)。 ### Q4:修改 zcclib 后 app 不更新? - 使用 `implementation(project(":zcclib"))` 时应自动重新编译。 - 若使用 `files("libs/xxx.aar")`,需 **重新执行 assembleRelease** 并 **替换 libs 中的 aar** 后 Sync。 ### Q5:双 LAUNCHER 图标? 删除 zcclib `AndroidManifest.xml` 中 Activity 的 `MAIN` / `LAUNCHER` intent-filter,仅保留 `exported` 与类名声明。 ### Q6:zcclib 和 app 的 minSdk 谁必须更低? **zcclib 必须 ≤ app**(app 必须 ≥ zcclib)。详见 [4.5 Minimum SDK 怎么配](#45-minimum-sdk-怎么配)。本工程:库 21、app 23,正确。 ### Q7:热修复里 AAR 的 versionName 和宿主 versionCode、TINKER_ID 怎么对应? 三者 **互不替代**:打补丁时改 `zcclib` 的 `versionName` 仅作 SDK 记录;**不要改** 宿主 `versionCode` 与 `TINKER_ID`(与是否同时改宿主业务代码无关)。详见 [第七章](#七热修复场景aar-与宿主工程的版本对应关系)、[第十二章](#十二热修复场景宿主-app-流程)。 ### Q8:AAR 的 Manifest 里新加一个 Activity,打补丁能成功吗? 分两种情况: | 情况 | 基准 APK 合并后的 Manifest | 能否靠补丁修好 | |------|---------------------------|----------------| | **A. 基准里已有该 Activity** | 发基准时 AAR 已声明,或宿主 Manifest 已声明 | **可以**。可热修该 Activity 的 Java、layout、strings 等(dex/res 差量) | | **B. 基准里没有,补丁里才新增** | 用户手机上的基准包从未注册该组件 | **不可靠 / 视为不支持**。应 **发新基准包**(`assembleRelease` + 更新 `patch/base` + 用户装新 APK) | **原因简述:** - 库 Manifest 在 **编译宿主 Release APK** 时与宿主 Manifest **合并**;`patch/base/app-release.apk` 里记下的是「当时」的组件列表。 - Tinker 虽可把 `AndroidManifest.xml` 打进资源补丁(TinkerDemo 的 `res.pattern` 已包含),但 **新增** Activity/Service 等组件,系统 `PackageManager` 未必像重装 APK 一样完整登记,常见现象是:补丁加载成功,`startActivity` 却 **ActivityNotFoundException** 或无法从桌面/隐式 Intent 拉起。 - 补丁里 **新增** 的 dex 类(新 Activity 的 `.class`)有时能进差量包,但 **没有基准里对应 Manifest 条目**,整体仍不算成功热修。 **推荐做法:** 1. **规划期**:新页面在发基准前就写进 `zcclib`(或宿主)Manifest,基准包一次带上;之后只热修逻辑/UI。 2. **已上线后真要加新页**:走 **场景 A 发新基准**,不要指望只换 AAR + `tinkerPatchRelease`。 3. **折中**:若必须补丁期加入口,可考虑不新增 Activity,而在 **基准里已有的 Activity**(如 `SecondActivity`)里改布局/Fragment 承载新 UI(仍在情况 A 范围内)。 --- ## 九、与本项目相关的文件清单 | 文件 | 作用 | |------|------| | `settings.gradle.kts` | `include(":zcclib")` | | `zcclib/build.gradle` | Library 插件与 SDK 配置 | | `zcclib/src/main/AndroidManifest.xml` | 权限、Activity 声明 | | `zcclib/src/main/java/com/example/zcclib/*.java` | 库业务代码 | | `app/build.gradle.kts` | `implementation(project(":zcclib"))` | | `app/src/main/java/.../MainActivity.java` | 调用库 API | | `gradle/libs.versions.toml` | 统一依赖版本 | | `zcclib/build/outputs/aar/zcclib-release.aar` | **最终发布的 AAR** | --- ## 十、快速命令备忘 ```powershell # 1. 打 Release AAR .\gradlew.bat :zcclib:assembleRelease # 2. 查看产物 # zcclib\build\outputs\aar\zcclib-release.aar # 3. 编译并运行调试 app(依赖工程模块) .\gradlew.bat :app:installDebug ``` --- ## 十一、打包 AAR 流程 将 **zcclib** 模块编译为 `.aar` 文件的标准流程(与是否集成到其他 App 无关)。 ### 11.1 前提 - `zcclib` 已配置为 Android Library(`plugins { id 'com.android.library' }`),见 [第三章](#三zcclib-模块配置详解)。 - 已在 `settings.gradle.kts` 中 `include(":zcclib")`。 ### 11.2 流程总览 ```mermaid flowchart TD A[修改 zcclib 代码/资源] --> B[可选:更新 versionName] B --> C[可选:app 模块联调] C --> D[执行 assembleRelease 或 assembleDebug] D --> E[在 outputs/aar 目录取 .aar 文件] ``` ### 11.3 操作步骤 #### 步骤 1:修改库代码(按需) - Java:`zcclib/src/main/java/com/example/zcclib/` - 资源:`zcclib/src/main/res/` - 组件声明:`zcclib/src/main/AndroidManifest.xml` #### 步骤 2:更新库版本号(可选) `zcclib/build.gradle`: ```gradle defaultConfig { versionCode 2 versionName "1.0.1" } ``` #### 步骤 3:本地验证(可选) 用本工程 `app` 模块做联调(`implementation(project(":zcclib"))`): ```powershell cd D:\AsWorkSpace\TestAar .\gradlew.bat :app:assembleDebug ``` #### 步骤 4:执行打包任务 在项目根目录打开终端: **Release(对外发布用这个):** ```powershell cd D:\AsWorkSpace\TestAar .\gradlew.bat :zcclib:assembleRelease ``` **Debug:** ```powershell .\gradlew.bat :zcclib:assembleDebug ``` 需要全量重编时先清理再打包: ```powershell .\gradlew.bat :zcclib:clean :zcclib:assembleRelease ``` **Android Studio:** 1. 右侧 **Gradle** → **TestAar** → **zcclib** → **Tasks** → **build** 2. 双击 **`assembleRelease`**(或 `assembleDebug`) 控制台出现 `BUILD SUCCESSFUL` 即表示打包完成。 #### 步骤 5:取出 AAR 文件 | 构建类型 | 输出文件 | |----------|----------| | Release | `zcclib/build/outputs/aar/zcclib-release.aar` | | Debug | `zcclib/build/outputs/aar/zcclib-debug.aar` | 完整路径示例: ``` D:\AsWorkSpace\TestAar\zcclib\build\outputs\aar\zcclib-release.aar ``` 确认文件已生成: ```powershell Get-Item "D:\AsWorkSpace\TestAar\zcclib\build\outputs\aar\zcclib-release.aar" ``` 将该 `.aar` 拷贝到任意目录备份或分发给其他工程即可;**打包流程到此结束**。 ### 11.4 Release 与 Debug 怎么选 | 类型 | 命令 | 说明 | |------|------|------| | **Release** | `assembleRelease` | 日常对外提供 SDK、集成到正式 App 时使用 | | **Debug** | `assembleDebug` | 带调试信息,一般仅本机临时验证 | ### 11.5 打包检查清单 ``` [ ] zcclib 代码 / 资源 / Manifest 已保存 [ ] 已执行 :zcclib:assembleRelease(或 assembleDebug) [ ] BUILD SUCCESSFUL [ ] 已在 zcclib/build/outputs/aar/ 下看到对应 .aar 文件 ``` ### 11.6 命令备忘 ```powershell cd D:\AsWorkSpace\TestAar .\gradlew.bat :zcclib:assembleRelease # 产物:zcclib\build\outputs\aar\zcclib-release.aar ``` --- ## 十二、热修复场景:宿主 App 流程 本章说明:在 **TestAar** 已按 [第十一章](#十一打包-aar-流程) 打出 `zcclib-release.aar` 之后,如何在 **Tinker 热修复宿主工程**(示例:**TinkerDemo**,`app/libs/zcclib-release.aar`)里完成集成、发基准包、以及 **打补丁(不让用户重装新 APK)** 时的宿主侧操作。 > **重要**:打补丁时 **可以** 同时修改宿主 App 的业务代码、`res`、以及 `libs` 里的 AAR;并不是「只能改 AAR」。区别在于 **发新基准包** vs **走 Tinker 补丁**,见 [12.2](#122-两种场景先选对再操作)。 版本号、`TINKER_ID`、`minSdk` 等对应关系见 [第七章](#七热修复场景aar-与宿主工程的版本对应关系);Tinker 完整集成见项目内文档或 [Android Tinker 热修复集成与使用指南](https://blog.csdn.net/xzytl60937234/article/details/161458824)。 ### 12.1 与第十一章的分工 | 阶段 | 在哪做 | 做什么 | |------|--------|--------| | 打 AAR | **TestAar** | `:zcclib:assembleRelease` → 得到 `zcclib-release.aar` | | 宿主集成 / 基准 / 补丁 | **TinkerDemo(宿主 app)** | 拷贝 aar、`assembleRelease` 或 `tinkerPatchRelease`、安装验证 | 手机上 **不会** 单独替换 `.aar` 文件;新版 AAR 会先编进宿主 APK,再与基准 APK 做差量生成补丁,用户加载补丁后生效。 ### 12.2 两种场景(先选对再操作) ```mermaid flowchart TD Q{手机上的 APK 是否已是基准包 且 patch/base 已就绪?} Q -->|否,首次接入| A[场景 A:发基准包] Q -->|是,修 bug 不发新 APK| B[场景 B:打补丁] A --> A1[拷贝 aar → assembleRelease → 保存 patch/base → 安装 APK] B --> B1[改宿主和/或 AAR → tinkerPatchRelease → 推补丁 → 冷启动] ``` | | 场景 A:首次接入 / 发新基准 | 场景 B:打补丁(不新发基准包) | |--|---------------------------|-------------------------------| | **何时用** | 第一次接入 zcclib,或必须发新整包时 | 用户已装 **基准 APK**;用补丁修 bug,**无需应用商店下发新 APK** | | **能改什么** | 宿主 + AAR 全量初始化 | **宿主代码/资源** 与 **AAR(覆盖 libs)** 可同时改;受 [7.5](#75-何时必须发新基准不能单靠打补丁) 约束 | | **TestAar** | 按需打 `zcclib-release.aar` | SDK 有改动时再打 aar;只改宿主则可不动 TestAar | | **宿主 `TINKER_ID`** | 设定或 **换新** | **必须不变** | | **`patch/base/`** | **生成并保存** 三件套 | **不要覆盖** | | **宿主 `versionCode`** | 按发版递增 | **通常不改** | | **宿主构建命令** | `assembleRelease` | `tinkerPatchRelease` | | **用户侧** | 安装新 APK | 加载补丁后 **冷启动** | --- ### 12.3 场景 A:宿主首次接入 AAR(发基准包) 在 **TinkerDemo** 工程(路径示例:`D:\AsWorkSpace\TinkerClaude`,以你本机为准)操作。 #### 步骤 1:放入 AAR 1. 从 TestAar 拷贝: `TestAar\zcclib\build\outputs\aar\zcclib-release.aar` 2. 放到宿主: `app\libs\zcclib-release.aar` #### 步骤 2:配置依赖 `app/build.gradle` 中已有或添加: ```gradle dependencies { implementation files('libs/zcclib-release.aar') // 并补齐 zcclib 使用的 AndroidX(appcompat、material 等),见本文 4.3 节 } ``` Sync 工程,确认无编译错误。 #### 步骤 3:打 Release 基准 APK ```powershell cd D:\AsWorkSpace\TinkerClaude .\gradlew.bat clean assembleRelease ``` #### 步骤 4:保存基准三件套(打后续补丁必用) ```powershell mkdir patch\base -Force copy app\build\outputs\apk\release\app-release.apk patch\base\ copy app\build\outputs\mapping\release\mapping.txt patch\base\ # AGP 8+ 资源 ID 文件(路径以本机构建输出为准) copy app\build\intermediates\stable_resource_ids_output\release\stableIds.txt patch\base\R.txt ``` 确认 `app/build.gradle` 里 `tinkerPatch` 的 `oldApk`、`applyMapping`、`applyResourceMapping` 指向 `patch/base/` 下上述文件。 #### 步骤 5:安装到手机(作为「线上基准」) ```powershell adb install -r app\build\outputs\apk\release\app-release.apk ``` 在 App 内验证能打开 AAR 页面(如 Demo 中「打开 aar 页面」→ `SecondActivity`)。 **此后** 若通过补丁修 bug(可只改宿主、只改 AAR、或两者都改),走 [12.4 场景 B](#124-场景-b打补丁宿主侧流程)。 --- ### 12.4 场景 B:打补丁(宿主侧流程) **前提:** 手机安装的 APK 与 `patch/base/app-release.apk` 一致(同一 `TINKER_ID`、同一基准构建)。 **可修改范围(常见组合):** | 修改目标 | 是否允许 | 操作要点 | |----------|----------|----------| | 宿主 Java/Kotlin(如 `MainActivity`) | ✅ | 直接改 `app/src/...`,参与 dex 差量 | | 宿主 `res`(strings、layout、颜色等) | ✅ | 参与资源差量 | | `libs/zcclib-release.aar`(SDK) | ✅ | TestAar 打新 aar 后 **覆盖** libs;未改 SDK 可跳过 | | 宿主 + AAR **一起改** | ✅ | 一次 `tinkerPatchRelease` 打出含多类差量的补丁 | | `TINKER_ID`、`patch/base`、换签名 | ❌ | 应走场景 A 发新基准 | | 基准 Manifest 里没有的新 Activity | ❌ | 需先发新基准把组件注册进 APK | #### 步骤 1:(按需)在 TestAar 打出新 AAR **仅当 SDK 有变更时** 执行 [第十一章](#十一打包-aar-流程): ```powershell cd D:\AsWorkSpace\TestAar .\gradlew.bat :zcclib:assembleRelease ``` 若本次只改宿主业务、未动 zcclib,**跳过本步**。 #### 步骤 2:(按需)覆盖宿主 libs 中的 AAR ```powershell Copy-Item "D:\AsWorkSpace\TestAar\zcclib\build\outputs\aar\zcclib-release.aar" ` "D:\AsWorkSpace\TinkerClaude\app\libs\zcclib-release.aar" -Force ``` - 文件名保持 **`zcclib-release.aar`**,依赖写法不变。 - 若只改宿主,本步可省略。 #### 步骤 3:修改宿主工程(按需) 在 **TinkerDemo** 的 `app/src/` 下修改业务代码或资源(与是否换 AAR 无关)。例如: - 改 `MainActivity` 逻辑 - 改 `res/values/strings.xml` 文案 - 与 AAR 内 `SecondActivity` 的修改 **可以同一天打进同一个补丁** **打补丁前仍须遵守:** 不修改 `TINKER_ID`、`versionCode`(通常)、`applicationId`;不覆盖 `patch/base/`。 #### 步骤 4:在宿主工程打补丁包 ```powershell cd D:\AsWorkSpace\TinkerClaude .\gradlew.bat clean .\gradlew.bat tinkerPatchRelease ``` 成功后在以下目录取补丁(**推这个,不要推 outputs 里可能过期的包**): ``` app\build\tmp\tinkerPatch\patch_signed_7zip.apk ``` #### 步骤 5:推送到手机并加载 ```powershell adb push app\build\tmp\tinkerPatch\patch_signed_7zip.apk /sdcard/patch_signed_7zip.apk ``` 在宿主 App 内使用「从外部存储加载补丁」等入口加载;加载成功后 **完全退出 App 再冷启动**。 #### 步骤 6:验证补丁是否生效 - 若改了 AAR:打开 AAR 内页面(如 `SecondActivity`),确认 **新版** 表现。 - 若改了宿主:检查对应界面/文案是否更新。 - logcat 过滤 `Tinker`,确认 `tryLoadPatchFiles: load end, ok!` 等成功日志。 --- ### 12.5 宿主侧检查清单 **场景 A(发基准)** ``` [ ] zcclib-release.aar 已放入 app/libs [ ] implementation files('libs/zcclib-release.aar') 已配置 [ ] AndroidX 依赖已补齐 [ ] assembleRelease 成功 [ ] patch/base 已保存 apk、mapping.txt、R.txt [ ] 手机已安装该基准 APK 并验证 AAR 页面可打开 ``` **场景 B(打补丁)** ``` [ ] 已确认应走补丁而非发新基准(见 7.5) [ ] 若 SDK 有变:TestAar 已 assembleRelease,且已覆盖 app/libs(同名) [ ] 若只改宿主:已保存 app/src 下代码/资源修改 [ ] 未改 TINKER_ID、versionCode、patch/base [ ] tinkerPatchRelease 成功 [ ] 已推送 build/tmp/tinkerPatch/patch_signed_7zip.apk [ ] App 内已加载补丁并冷启动 [ ] 宿主与/或 AAR 侧改动均已验证 ``` ### 12.6 宿主命令备忘 ```powershell # —— 场景 A:发基准 —— cd D:\AsWorkSpace\TinkerClaude .\gradlew.bat assembleRelease adb install -r app\build\outputs\apk\release\app-release.apk # —— 场景 B:打补丁(改完宿主和/或 libs 内 aar 后)—— cd D:\AsWorkSpace\TinkerClaude .\gradlew.bat tinkerPatchRelease adb push app\build\tmp\tinkerPatch\patch_signed_7zip.apk /sdcard/patch_signed_7zip.apk ``` ### 12.7 常见错误(宿主侧) | 现象 | 可能原因 | 处理 | |------|----------|------| | 补丁打了但界面没变 | 手机不是基准包,或推错补丁文件 | 重装 `patch/base` 对应 APK;只推 `tmp/tinkerPatch` 下最新包 | | `tinkerPatchRelease` 失败 | mapping / R.txt 与基准不一致 | 勿随意改 `patch/base`;大改资源应走场景 A 发新基准 | | 以为只能改 AAR、不敢改宿主 | 文档旧称「仅热修 AAR」易误解 | 宿主与 AAR 均可改;限制是基准契约,见 12.2 / 7.5 | | 改了代码却让用户装新 APK | 误走 `assembleRelease` 发整包 | 修 bug 应 `tinkerPatchRelease` + 补丁 | | 合成警告 loader 类变化 | R8 重编译差异 | 宿主 `ignoreWarning = true`(Demo 已配置) | 更细的版本约束与「何时必须发新基准」见 [第七章 7.4~7.5](#74-两种场景对照不要混用)。 --- *文档版本:与 TestAar 工程 zcclib / app 当前配置同步(AGP 8.6.0,Gradle 8.7,compileSdk 34)。*