# 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)。*