Android app 项目:模块打包 AAR 教程
本文档基于当前 TestAar 工程的实际配置编写,说明如何新增/维护 zcclib (Android Library)模块、如何在 app 中引用,以及如何生成并在其他工程中使用 AAR 文件。
命名说明 :本仓库模块名为
zcclib(包名com.example.zcclib)。若你习惯称为 zzclib,仅名称不同,步骤一致。热修复集成 :将 AAR 打进 Tinker 宿主时的版本关系见 第七章。
打包流程 :将 zcclib 打成
.aar的步骤见 第十一章。热修宿主流程 :AAR 接入 Tinker 宿主 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(推荐)
- 菜单 File → New → New Module...
- 选择 Android Library,Next
- 填写:
- Module name :
zcclib - Package name :
com.example.zcclib(与namespace保持一致) - Language:Java(与本项目一致)
- Minimum SDK :库的值应 ≤ app(本库 21,app 23;规则见 4.5)
- Module name :
- Finish 后,Studio 会自动在
settings.gradle.kts中加入include(":zcclib")。
方式 B:手动创建目录
-
复制本仓库
zcclib/目录结构,或新建:zcclib/
├── build.gradle
├── proguard-rules.pro
└── src/main/
├── AndroidManifest.xml
├── java/com/example/zcclib/
└── res/ -
在
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
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<!-- 其他权限按需添加 -->
<application
android:theme="@style/Theme.TestAar">
<activity
android:name=".MainActivity"
android:exported="true" />
<activity
android:name=".SecondActivity"
android:exported="true" />
</application>
</manifest>
发布 AAR 时的建议:
- 库内 Activity 建议设置
android:exported="true"(Android 12+ 要求显式声明),由宿主通过显式Intent跳转。 - 不要 在对外发布的 AAR 里保留
<intent-filter> MAIN / LAUNCHER(本仓库若用于单独调试库可保留;交付第三方时应删掉,避免宿主出现双启动图标)。 - 库 Manifest 中的
<application>属性会与宿主合并,注意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 文件
- 将 AAR 复制到宿主工程,例如:
app/libs/zcclib-release.aar - 修改
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"))))
}
- 必须 在宿主 app 中补充 zcclib 使用到的 implementation 依赖(AAR 不包含这些 jar/aar):
kotlin
implementation(libs.appcompat)
implementation(libs.material)
implementation(libs.activity)
implementation(libs.constraintlayout)
-
若 zcclib 的
AndroidManifest中有权限、Activity、Service 等,合并后宿主 无需 再手写一遍(除非要覆盖exported等属性)。 -
宿主与库的 minSdk 关系 见 [4.5 Minimum SDK 怎么配](#4.5 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
- 右侧 Gradle 面板 → TestAar → zcclib → Tasks → build
- 双击
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 文件)
- 复制
zcclib-release.aar到目标工程app/libs/ - 按 4.3 配置依赖与 AndroidX 库
- 调用
SecondActivity.start(context)或MainActivity.start(context) - 若闪退,检查 logcat:常见原因为缺少
appcompat/material依赖,或 Activity 未exported、Manifest 未合并
七、热修复场景:AAR 与宿主工程的版本对应关系
将 zcclib-release.aar 集成到 Tinker 热修复宿主 (本仓库旁路工程 TinkerDemo ,app/libs/zcclib-release.aar)时,除了 minSdk 外,还要区分好几套「版本」------它们 不是同一个字段 ,也不能用 AAR 的 versionName 去和 TINKER_ID 划等号。
更完整的热修流程见:Android Tinker 热修复集成与使用指南 1.9.15.2
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-svg-G3dZHSByS6C4VahF{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-G3dZHSByS6C4VahF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-G3dZHSByS6C4VahF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-G3dZHSByS6C4VahF .error-icon{fill:#552222;}#mermaid-svg-G3dZHSByS6C4VahF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-G3dZHSByS6C4VahF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-G3dZHSByS6C4VahF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-G3dZHSByS6C4VahF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-G3dZHSByS6C4VahF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-G3dZHSByS6C4VahF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-G3dZHSByS6C4VahF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-G3dZHSByS6C4VahF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-G3dZHSByS6C4VahF .marker.cross{stroke:#333333;}#mermaid-svg-G3dZHSByS6C4VahF svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-G3dZHSByS6C4VahF p{margin:0;}#mermaid-svg-G3dZHSByS6C4VahF .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-G3dZHSByS6C4VahF .cluster-label text{fill:#333;}#mermaid-svg-G3dZHSByS6C4VahF .cluster-label span{color:#333;}#mermaid-svg-G3dZHSByS6C4VahF .cluster-label span p{background-color:transparent;}#mermaid-svg-G3dZHSByS6C4VahF .label text,#mermaid-svg-G3dZHSByS6C4VahF span{fill:#333;color:#333;}#mermaid-svg-G3dZHSByS6C4VahF .node rect,#mermaid-svg-G3dZHSByS6C4VahF .node circle,#mermaid-svg-G3dZHSByS6C4VahF .node ellipse,#mermaid-svg-G3dZHSByS6C4VahF .node polygon,#mermaid-svg-G3dZHSByS6C4VahF .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-G3dZHSByS6C4VahF .rough-node .label text,#mermaid-svg-G3dZHSByS6C4VahF .node .label text,#mermaid-svg-G3dZHSByS6C4VahF .image-shape .label,#mermaid-svg-G3dZHSByS6C4VahF .icon-shape .label{text-anchor:middle;}#mermaid-svg-G3dZHSByS6C4VahF .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-G3dZHSByS6C4VahF .rough-node .label,#mermaid-svg-G3dZHSByS6C4VahF .node .label,#mermaid-svg-G3dZHSByS6C4VahF .image-shape .label,#mermaid-svg-G3dZHSByS6C4VahF .icon-shape .label{text-align:center;}#mermaid-svg-G3dZHSByS6C4VahF .node.clickable{cursor:pointer;}#mermaid-svg-G3dZHSByS6C4VahF .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-G3dZHSByS6C4VahF .arrowheadPath{fill:#333333;}#mermaid-svg-G3dZHSByS6C4VahF .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-G3dZHSByS6C4VahF .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-G3dZHSByS6C4VahF .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-G3dZHSByS6C4VahF .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-G3dZHSByS6C4VahF .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-G3dZHSByS6C4VahF .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-G3dZHSByS6C4VahF .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-G3dZHSByS6C4VahF .cluster text{fill:#333;}#mermaid-svg-G3dZHSByS6C4VahF .cluster span{color:#333;}#mermaid-svg-G3dZHSByS6C4VahF div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-G3dZHSByS6C4VahF .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-G3dZHSByS6C4VahF rect.text{fill:none;stroke-width:0;}#mermaid-svg-G3dZHSByS6C4VahF .icon-shape,#mermaid-svg-G3dZHSByS6C4VahF .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-G3dZHSByS6C4VahF .icon-shape p,#mermaid-svg-G3dZHSByS6C4VahF .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-G3dZHSByS6C4VahF .icon-shape .label rect,#mermaid-svg-G3dZHSByS6C4VahF .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-G3dZHSByS6C4VahF .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-G3dZHSByS6C4VahF .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-G3dZHSByS6C4VahF :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 打补丁 不新发基准
改 AAR 和/或 宿主代码资源
tinkerPatchRelease
TINKER_ID 不变
推补丁 + 冷启动
发基准 / 首次接入 AAR
TestAar 打出 zcclib-release.aar
拷贝到 TinkerDemo app/libs
assembleRelease
保存 patch/base 三件套
用户安装基准 APK
| 步骤 | 发基准(含某版 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 推荐版本管理习惯
- AAR :
zcclib的versionName与 Git tag 对齐(如1.0.1),每次对外发包递增。 - 宿主 :
versionName表示 App 发版;打补丁不发新基准时不升 versionCode。 - 热修 :用
TINKER_ID绑定「哪次基准」;patchVersion仅表示补丁序号。 - 在 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 怎么配](#4.5 Minimum SDK 怎么配)。本工程:库 21、app 23,正确。
Q7:热修复里 AAR 的 versionName 和宿主 versionCode、TINKER_ID 怎么对应?
三者 互不替代 :打补丁时改 zcclib 的 versionName 仅作 SDK 记录;不要改 宿主 versionCode 与 TINKER_ID(与是否同时改宿主业务代码无关)。详见 第七章、第十二章。
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 条目,整体仍不算成功热修。
推荐做法:
- 规划期 :新页面在发基准前就写进
zcclib(或宿主)Manifest,基准包一次带上;之后只热修逻辑/UI。 - 已上线后真要加新页 :走 场景 A 发新基准 ,不要指望只换 AAR +
tinkerPatchRelease。 - 折中 :若必须补丁期加入口,可考虑不新增 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' }),见 第三章。- 已在
settings.gradle.kts中include(":zcclib")。
11.2 流程总览
#mermaid-svg-AtKPpoCHpRZ1VirT{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-AtKPpoCHpRZ1VirT .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-AtKPpoCHpRZ1VirT .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-AtKPpoCHpRZ1VirT .error-icon{fill:#552222;}#mermaid-svg-AtKPpoCHpRZ1VirT .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-AtKPpoCHpRZ1VirT .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-AtKPpoCHpRZ1VirT .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-AtKPpoCHpRZ1VirT .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-AtKPpoCHpRZ1VirT .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-AtKPpoCHpRZ1VirT .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-AtKPpoCHpRZ1VirT .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-AtKPpoCHpRZ1VirT .marker{fill:#333333;stroke:#333333;}#mermaid-svg-AtKPpoCHpRZ1VirT .marker.cross{stroke:#333333;}#mermaid-svg-AtKPpoCHpRZ1VirT svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-AtKPpoCHpRZ1VirT p{margin:0;}#mermaid-svg-AtKPpoCHpRZ1VirT .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-AtKPpoCHpRZ1VirT .cluster-label text{fill:#333;}#mermaid-svg-AtKPpoCHpRZ1VirT .cluster-label span{color:#333;}#mermaid-svg-AtKPpoCHpRZ1VirT .cluster-label span p{background-color:transparent;}#mermaid-svg-AtKPpoCHpRZ1VirT .label text,#mermaid-svg-AtKPpoCHpRZ1VirT span{fill:#333;color:#333;}#mermaid-svg-AtKPpoCHpRZ1VirT .node rect,#mermaid-svg-AtKPpoCHpRZ1VirT .node circle,#mermaid-svg-AtKPpoCHpRZ1VirT .node ellipse,#mermaid-svg-AtKPpoCHpRZ1VirT .node polygon,#mermaid-svg-AtKPpoCHpRZ1VirT .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-AtKPpoCHpRZ1VirT .rough-node .label text,#mermaid-svg-AtKPpoCHpRZ1VirT .node .label text,#mermaid-svg-AtKPpoCHpRZ1VirT .image-shape .label,#mermaid-svg-AtKPpoCHpRZ1VirT .icon-shape .label{text-anchor:middle;}#mermaid-svg-AtKPpoCHpRZ1VirT .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-AtKPpoCHpRZ1VirT .rough-node .label,#mermaid-svg-AtKPpoCHpRZ1VirT .node .label,#mermaid-svg-AtKPpoCHpRZ1VirT .image-shape .label,#mermaid-svg-AtKPpoCHpRZ1VirT .icon-shape .label{text-align:center;}#mermaid-svg-AtKPpoCHpRZ1VirT .node.clickable{cursor:pointer;}#mermaid-svg-AtKPpoCHpRZ1VirT .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-AtKPpoCHpRZ1VirT .arrowheadPath{fill:#333333;}#mermaid-svg-AtKPpoCHpRZ1VirT .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-AtKPpoCHpRZ1VirT .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-AtKPpoCHpRZ1VirT .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AtKPpoCHpRZ1VirT .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-AtKPpoCHpRZ1VirT .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AtKPpoCHpRZ1VirT .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-AtKPpoCHpRZ1VirT .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-AtKPpoCHpRZ1VirT .cluster text{fill:#333;}#mermaid-svg-AtKPpoCHpRZ1VirT .cluster span{color:#333;}#mermaid-svg-AtKPpoCHpRZ1VirT div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-AtKPpoCHpRZ1VirT .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-AtKPpoCHpRZ1VirT rect.text{fill:none;stroke-width:0;}#mermaid-svg-AtKPpoCHpRZ1VirT .icon-shape,#mermaid-svg-AtKPpoCHpRZ1VirT .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AtKPpoCHpRZ1VirT .icon-shape p,#mermaid-svg-AtKPpoCHpRZ1VirT .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-AtKPpoCHpRZ1VirT .icon-shape .label rect,#mermaid-svg-AtKPpoCHpRZ1VirT .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AtKPpoCHpRZ1VirT .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-AtKPpoCHpRZ1VirT .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-AtKPpoCHpRZ1VirT :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 修改 zcclib 代码/资源
可选:更新 versionName
可选:app 模块联调
执行 assembleRelease 或 assembleDebug
在 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:
- 右侧 Gradle → TestAar → zcclib → Tasks → build
- 双击
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 已按 第十一章 打出 zcclib-release.aar 之后,如何在 Tinker 热修复宿主工程 (示例:TinkerDemo ,app/libs/zcclib-release.aar)里完成集成、发基准包、以及 打补丁(不让用户重装新 APK) 时的宿主侧操作。
重要 :打补丁时 可以 同时修改宿主 App 的业务代码、
res、以及libs里的 AAR;并不是「只能改 AAR」。区别在于 发新基准包 vs 走 Tinker 补丁 ,见 12.2。
版本号、TINKER_ID、minSdk 等对应关系见 第七章;Tinker 完整集成见项目内文档或 Android Tinker 热修复集成与使用指南。
12.1 与第十一章的分工
| 阶段 | 在哪做 | 做什么 |
|---|---|---|
| 打 AAR | TestAar | :zcclib:assembleRelease → 得到 zcclib-release.aar |
| 宿主集成 / 基准 / 补丁 | TinkerDemo(宿主 app) | 拷贝 aar、assembleRelease 或 tinkerPatchRelease、安装验证 |
手机上 不会 单独替换 .aar 文件;新版 AAR 会先编进宿主 APK,再与基准 APK 做差量生成补丁,用户加载补丁后生效。
12.2 两种场景(先选对再操作)
#mermaid-svg-J8N76bg01Pql6303{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-J8N76bg01Pql6303 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-J8N76bg01Pql6303 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-J8N76bg01Pql6303 .error-icon{fill:#552222;}#mermaid-svg-J8N76bg01Pql6303 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-J8N76bg01Pql6303 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-J8N76bg01Pql6303 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-J8N76bg01Pql6303 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-J8N76bg01Pql6303 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-J8N76bg01Pql6303 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-J8N76bg01Pql6303 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-J8N76bg01Pql6303 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-J8N76bg01Pql6303 .marker.cross{stroke:#333333;}#mermaid-svg-J8N76bg01Pql6303 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-J8N76bg01Pql6303 p{margin:0;}#mermaid-svg-J8N76bg01Pql6303 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-J8N76bg01Pql6303 .cluster-label text{fill:#333;}#mermaid-svg-J8N76bg01Pql6303 .cluster-label span{color:#333;}#mermaid-svg-J8N76bg01Pql6303 .cluster-label span p{background-color:transparent;}#mermaid-svg-J8N76bg01Pql6303 .label text,#mermaid-svg-J8N76bg01Pql6303 span{fill:#333;color:#333;}#mermaid-svg-J8N76bg01Pql6303 .node rect,#mermaid-svg-J8N76bg01Pql6303 .node circle,#mermaid-svg-J8N76bg01Pql6303 .node ellipse,#mermaid-svg-J8N76bg01Pql6303 .node polygon,#mermaid-svg-J8N76bg01Pql6303 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-J8N76bg01Pql6303 .rough-node .label text,#mermaid-svg-J8N76bg01Pql6303 .node .label text,#mermaid-svg-J8N76bg01Pql6303 .image-shape .label,#mermaid-svg-J8N76bg01Pql6303 .icon-shape .label{text-anchor:middle;}#mermaid-svg-J8N76bg01Pql6303 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-J8N76bg01Pql6303 .rough-node .label,#mermaid-svg-J8N76bg01Pql6303 .node .label,#mermaid-svg-J8N76bg01Pql6303 .image-shape .label,#mermaid-svg-J8N76bg01Pql6303 .icon-shape .label{text-align:center;}#mermaid-svg-J8N76bg01Pql6303 .node.clickable{cursor:pointer;}#mermaid-svg-J8N76bg01Pql6303 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-J8N76bg01Pql6303 .arrowheadPath{fill:#333333;}#mermaid-svg-J8N76bg01Pql6303 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-J8N76bg01Pql6303 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-J8N76bg01Pql6303 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-J8N76bg01Pql6303 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-J8N76bg01Pql6303 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-J8N76bg01Pql6303 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-J8N76bg01Pql6303 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-J8N76bg01Pql6303 .cluster text{fill:#333;}#mermaid-svg-J8N76bg01Pql6303 .cluster span{color:#333;}#mermaid-svg-J8N76bg01Pql6303 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-J8N76bg01Pql6303 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-J8N76bg01Pql6303 rect.text{fill:none;stroke-width:0;}#mermaid-svg-J8N76bg01Pql6303 .icon-shape,#mermaid-svg-J8N76bg01Pql6303 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-J8N76bg01Pql6303 .icon-shape p,#mermaid-svg-J8N76bg01Pql6303 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-J8N76bg01Pql6303 .icon-shape .label rect,#mermaid-svg-J8N76bg01Pql6303 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-J8N76bg01Pql6303 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-J8N76bg01Pql6303 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-J8N76bg01Pql6303 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否,首次接入
是,修 bug 不发新 APK
手机上的 APK 是否已是基准包 且 patch/base 已就绪?
场景 A:发基准包
场景 B:打补丁
拷贝 aar → assembleRelease → 保存 patch/base → 安装 APK
改宿主和/或 AAR → tinkerPatchRelease → 推补丁 → 冷启动
| 场景 A:首次接入 / 发新基准 | 场景 B:打补丁(不新发基准包) | |
|---|---|---|
| 何时用 | 第一次接入 zcclib,或必须发新整包时 | 用户已装 基准 APK ;用补丁修 bug,无需应用商店下发新 APK |
| 能改什么 | 宿主 + AAR 全量初始化 | 宿主代码/资源 与 AAR(覆盖 libs) 可同时改;受 [7.5](#场景 A:首次接入 / 发新基准 场景 B:打补丁(不新发基准包) 何时用 第一次接入 zcclib,或必须发新整包时 用户已装 基准 APK;用补丁修 bug,无需应用商店下发新 APK 能改什么 宿主 + AAR 全量初始化 宿主代码/资源 与 AAR(覆盖 libs) 可同时改;受 7.5 约束 TestAar 按需打 zcclib-release.aar SDK 有改动时再打 aar;只改宿主则可不动 TestAar 宿主 TINKER_ID 设定或 换新 必须不变 patch/base/ 生成并保存 三件套 不要覆盖 宿主 versionCode 按发版递增 通常不改 宿主构建命令 assembleRelease tinkerPatchRelease 用户侧 安装新 APK 加载补丁后 冷启动) 约束 |
| 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
- 从 TestAar 拷贝:
TestAar\zcclib\build\outputs\aar\zcclib-release.aar - 放到宿主:
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](#12.4 场景 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 有变更时 执行 第十一章:
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](#第七章 7.4~7.5)。
文档版本:与 TestAar 工程 zcclib / app 当前配置同步(AGP 8.6.0,Gradle 8.7,compileSdk 34)。