Android app 项目:模块打包 AAR 教程

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(推荐)

  1. 菜单 File → New → New Module...
  2. 选择 Android Library,Next
  3. 填写:
    • Module namezcclib
    • Package namecom.example.zcclib(与 namespace 保持一致)
    • Language:Java(与本项目一致)
    • Minimum SDK :库的值应 app(本库 21,app 23;规则见 4.5
  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 复制代码
<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:iconandroid: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"))))
}
  1. 必须 在宿主 app 中补充 zcclib 使用到的 implementation 依赖(AAR 不包含这些 jar/aar):
kotlin 复制代码
implementation(libs.appcompat)
implementation(libs.material)
implementation(libs.activity)
implementation(libs.constraintlayout)
  1. 若 zcclib 的 AndroidManifest 中有权限、Activity、Service 等,合并后宿主 无需 再手写一遍(除非要覆盖 exported 等属性)。

  2. 宿主与库的 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

  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 配置依赖与 AndroidX 库
  3. 调用 SecondActivity.start(context)MainActivity.start(context)
  4. 若闪退,检查 logcat:常见原因为缺少 appcompat/material 依赖,或 Activity 未 exported、Manifest 未合并

七、热修复场景:AAR 与宿主工程的版本对应关系

zcclib-release.aar 集成到 Tinker 热修复宿主 (本仓库旁路工程 TinkerDemoapp/libs/zcclib-release.aar)时,除了 minSdk 外,还要区分好几套「版本」------它们 不是同一个字段 ,也不能用 AAR 的 versionName 去和 TINKER_ID 划等号。

更完整的热修流程见:Android Tinker 热修复集成与使用指南 1.9.15.2

7.1 先分清三套「版本」

名称 配置位置 作用
AAR / 库版本 zcclib/build.gradleversionCodeversionName 写入 AAR 元数据,便于人工区分 SDK 第几版;Tinker 不拿它做补丁校验
宿主 APK 版本 热修复工程 app/build.gradleversionCodeversionName 应用商店/用户看到的版本;打 补丁 时通常 不改
热修基准标识 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.apkmapping.txtR.txt
打补丁(含改 AAR / 改宿主代码) 保持不变 不要覆盖
新渠道/大改版/换签名/改 applicationId 必须换新 用新 APK 整包替换
AndroidX 等「未打进 AAR」的依赖
  • AAR 不含 appcompatmaterial 等;宿主必须自行 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、assetslibs 下 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_IDpatch/base 三件套、未在基准 Manifest 注册的组件等),而不是「宿主不能改」。

7.5 何时必须发新基准(不能单靠打补丁)

出现以下情况时,应 提高宿主 versionCode、修改 TINKER_ID、重新 assembleRelease 并更新 patch/base ,而不是继续 tinkerPatchRelease

  • 修改了 applicationIdTINKER_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 内 versionName1.01.0.1
  • 不改 applicationId、不改 TINKER_ID、不破坏 patch/base 与基准 Manifest 契约

7.6 推荐版本管理习惯

  1. AARzcclibversionName 与 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 怎么配](#4.5 Minimum SDK 怎么配)。本工程:库 21、app 23,正确。

Q7:热修复里 AAR 的 versionName 和宿主 versionCode、TINKER_ID 怎么对应?

三者 互不替代 :打补丁时改 zcclibversionName 仅作 SDK 记录;不要改 宿主 versionCodeTINKER_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 一样完整登记,常见现象是:补丁加载成功,startActivityActivityNotFoundException 或无法从桌面/隐式 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' }),见 第三章
  • 已在 settings.gradle.ktsinclude(":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:

  1. 右侧 GradleTestAarzcclibTasksbuild
  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 已按 第十一章 打出 zcclib-release.aar 之后,如何在 Tinker 热修复宿主工程 (示例:TinkerDemoapp/libs/zcclib-release.aar)里完成集成、发基准包、以及 打补丁(不让用户重装新 APK) 时的宿主侧操作。

重要 :打补丁时 可以 同时修改宿主 App 的业务代码、res、以及 libs 里的 AAR;并不是「只能改 AAR」。区别在于 发新基准包 vs 走 Tinker 补丁 ,见 12.2

版本号、TINKER_IDminSdk 等对应关系见 第七章;Tinker 完整集成见项目内文档或 Android Tinker 热修复集成与使用指南

12.1 与第十一章的分工

阶段 在哪做 做什么
打 AAR TestAar :zcclib:assembleRelease → 得到 zcclib-release.aar
宿主集成 / 基准 / 补丁 TinkerDemo(宿主 app) 拷贝 aar、assembleReleasetinkerPatchRelease、安装验证

手机上 不会 单独替换 .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
  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.gradletinkerPatcholdApkapplyMappingapplyResourceMapping 指向 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_IDpatch/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:修改宿主工程(按需)

TinkerDemoapp/src/ 下修改业务代码或资源(与是否换 AAR 无关)。例如:

  • MainActivity 逻辑
  • res/values/strings.xml 文案
  • 与 AAR 内 SecondActivity 的修改 可以同一天打进同一个补丁

打补丁前仍须遵守: 不修改 TINKER_IDversionCode(通常)、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)。

相关推荐
ImTryCatchException1 小时前
React Native 嵌入现有 Android 项目:踩坑记录与解决方案
android·react native·react.js
曼岛_1 小时前
[安卓逆向]在Android Studio中编写SO文件并测试调用 (四)
android·ide·android studio
ImTryCatchException2 小时前
Android 卡顿诊断 SDK:从痛点出发的设计思考
android·gitee
流星白龙2 小时前
【MySQL高阶】14.MySQL存储结构
android·数据库·mysql
流星白龙2 小时前
【MySQL高阶】15.MySQL存储结构,页结构
android·mysql·adb
赏金术士2 小时前
Android Tinker Demo 使用手册
android·热修复·tinker
Meteors.3 小时前
Kotlin协程序使用技巧和应用场景
android·开发语言·kotlin
黄林晴3 小时前
官方实战指南!Compose 项目无缝迁移 KMP
android·kotlin
tryqaaa_3 小时前
学习日志(五)【php反序列化全加例题】【pop链,字符逃逸,session,伪协议】
android·学习·php·web·pop·session