Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Gradle 与工程化

中级面试常问构建系统和工程化能力,体现你能不能搭/维护一个中大型项目。你做 SDK 对构建、依赖、产物体积本就敏感,这块容易讲出深度。

一、Gradle 基础

  • Gradle:基于 JVM 的构建工具,用 Groovy/Kotlin DSL(.kts) 写脚本。Android 用 AGP(Android Gradle Plugin)。
  • 构建生命周期三阶段:
    1. 初始化(Initialization):确定哪些模块参与构建(settings.gradle)。
    2. 配置(Configuration):执行所有 build.gradle,构建任务依赖图(Task DAG)。这阶段慢会拖累整体
    3. 执行(Execution):按依赖图执行需要的 Task。
  • Task:构建的最小执行单元,有输入/输出,支持增量。
  • 核心文件:settings.gradle(.kts)(模块声明)、build.gradle(.kts)(模块配置)、gradle.properties(全局配置)。

二、依赖管理

  • 依赖配置:
    • implementation:依赖不传递(只本模块可见),编译隔离、加快构建,首选。
    • api:依赖传递给上层模块(谨慎用,会扩大编译范围)。
    • compileOnly:只编译期(如注解处理、provided)。
    • runtimeOnlytestImplementationkapt/ksp(注解处理)。
  • Version Catalog(libs.versions.toml):集中管理依赖版本,多模块统一,现代推荐。
  • 依赖冲突:Gradle 默认选最高版本;可用 resolutionStrategy 强制版本、exclude 排除传递依赖。
  • BOM:统一一组库的版本(如 Compose BOM)。

三、构建变体与产物

  • buildTypes:debug / release(混淆、签名、是否可调试)。
  • productFlavors:多渠道/多环境(免费版/付费版、国内/海外),组合成 variant
  • 签名配置 signingConfigs:配置 keystore。
  • manifestPlaceholders / BuildConfig 字段:按变体注入不同配置(如不同 API 域名、key)。

四、组件化 / 模块化

中大型项目的核心架构:

  • 为什么组件化:编译解耦(改一个模块不全量编)、并行开发、复用、可独立运行调试。
  • 分层:app 壳 → 业务模块(feature) → 基础库(common/network/ui)。
  • 模块通信(解耦):模块间不直接依赖,用路由(ARouter) + 接口下沉(sink) + DI 组合;Hilt 更偏依赖注入,不是页面路由本身。
  • 路由 ARouter 机制:
    1. 业务页面用注解声明路径,如 @Route(path = "/user/profile")
    2. 编译期 APT 扫描注解,为每个模块生成路由表类,记录 path → Activity/Fragment/Provider 的映射。
    3. App 启动或首次使用时加载各模块路由表。
    4. 调用 ARouter.getInstance().build("/user/profile").withString("id", id).navigation() 时,框架按 path 找到目标并完成参数注入/Intent 跳转。
  • 接口下沉(sink):把跨模块能力抽到公共 API 模块,例如 :user-api 只放 UserService 接口和数据模型,:feature-order 依赖接口而不依赖 :feature-user 实现。实现模块通过路由 Provider 或 DI 绑定暴露能力。
  • 与 Hilt/DI 的区别:路由解决“页面/服务如何按路径发现和跳转”,DI 解决“对象依赖如何创建和注入”。组件化里常组合使用:ARouter 做跨模块入口,Hilt 给模块内部或接口实现注入依赖。
// :user-api
interface UserService {
    fun currentUserId(): String?
}

// :feature-order 只依赖 user-api,不依赖 feature-user
class OrderViewModel(
    private val userService: UserService
) : ViewModel()

五、构建优化(你的兴趣点)

  • 配置阶段优化:避免在配置期做耗时操作、用 configuration cache
  • configuration cache 边界:它缓存配置阶段产物,命中时可跳过配置阶段;但构建脚本、Version Catalog、gradle.properties、环境变量/系统属性读取、配置期文件读取、自定义插件逻辑等配置输入变化都会导致失效。采用前要修复 Gradle 报告的 configuration cache problems,不要只开开关。
  • 增量编译 / 构建缓存:org.gradle.caching=true,复用未变模块的产物。
  • 并行构建:org.gradle.parallel=true,多模块并行。
  • 守护进程:Gradle Daemon 常驻避免 JVM 重启。
  • KSP 替代 KAPT:KSP 直接分析 Kotlin 符号,比 KAPT(生成 Java stub)快很多。
  • 模块化:拆模块 + implementation 隔离,减少改动的重编范围。
  • AGP 升级:新版本构建性能持续优化。
  • 产物优化:R8 裁剪、资源压缩、so 按 ABI 拆分(见 10、11 篇,你的强项)。

六、测试

  • 单元测试:JUnit + Mockito/MockK(mock 依赖),测纯逻辑(ViewModel、UseCase)。
  • 协程测试:runTest + TestDispatcher(StandardTestDispatcher/UnconfinedTestDispatcher),Turbine 测 Flow。
  • UI 测试:Espresso(View)、Compose Test Rule(createComposeRule)。
  • 测试金字塔:大量单元测试 + 适量集成 + 少量 UI 测试。
  • 可测试性:依赖注入 + 接口抽象,让逻辑可替换 mock(见 08 篇架构)。

七、Android 版本适配(高频)

  • 运行时权限(6.0+):危险权限动态申请;后续版本继续细分一次性权限、后台定位、照片/视频/音频等权限边界。
  • 分区存储 Scoped Storage(10/11+):App 只能自由访问自己目录 + MediaStore,访问其他需 SAF;具体强制行为和兼容开关与 Android 10/11、targetSdk 有关,升级时要按官方行为变更表核对。
  • 后台限制(8.0+):后台 Service 受限、隐式广播限制、用 WorkManager/JobScheduler;Android 12(API 31) 起对 target 31+ 的后台启动前台服务限制更严格,不满足例外会抛异常。
  • 通知渠道(8.0+):必须建 NotificationChannel;Android 13(API 33) target 33+ 需要申请 POST_NOTIFICATIONS,用户拒绝后普通通知不可见,但前台服务仍会在系统规定位置保留可见性。
  • targetSdk 升级:每次升级要处理对应行为变更,不要只改数字。
    • Android 12 / API 31:target 31+ 创建 PendingIntent 必须显式声明 FLAG_IMMUTABLEFLAG_MUTABLE;后台启动 FGS、通知 trampoline、精确闹钟等也有新限制。
    • Android 13 / API 33:通知运行时权限、细分媒体权限、部分组件导出/Intent 安全要求需要排查。
    • Android 14 / API 34:target 34+ 前台服务必须声明合适的 foreground service type;隐式 Intent 发送到应用内部未导出组件、mutable implicit PendingIntent 等行为更受限制。
  • 适配排查清单:先读官方 behavior changes → 全局搜索权限/通知/FGS/PendingIntent/存储/API 调用 → 加兼容分支和自动化测试 → 用灰度观察崩溃、ANR、权限拒绝率。
  • 64 位要求:Google Play 强制 arm64,需提供 64 位 so。

高频面试题

Q1:implementation 和 api 区别? implementation 依赖不传递,只本模块可见,编译隔离、加快构建;api 会把依赖暴露给上层模块(传递依赖),扩大编译范围。优先 implementation,仅需对外暴露时用 api。

Q2:Gradle 构建有哪几个阶段?哪个容易成为瓶颈? 初始化(定模块)、配置(执行所有 build.gradle 建任务图)、执行(跑 Task)。配置阶段会执行所有模块脚本,模块多/脚本重时成为瓶颈,可用配置缓存优化。

Q3:为什么要组件化?模块间怎么通信? 编译解耦(加快构建)、并行开发、复用、独立调试。模块间不直接依赖,通过路由(ARouter)+ 接口下沉 + 依赖注入通信,避免循环依赖。

Q4:怎么优化 Gradle 构建速度? 开启构建缓存、并行构建、配置缓存、Daemon;用 KSP 替代 KAPT;模块化 + implementation 隔离减小重编范围;升级 AGP/Gradle。

Q5:KAPT 和 KSP 区别? KAPT 为兼容 Java 注解处理器会生成 Java stub,慢;KSP 是 Kotlin 原生符号处理,直接分析 Kotlin 代码,速度快数倍,Room/Hilt 等已支持。

Q6:分区存储是什么?带来什么变化? Android 10+ 限制 App 只能自由访问自己的外部目录和 MediaStore,访问其他文件需通过 SAF 或 MediaStore API,不能再随意读写整个外部存储,提升隐私。

Q7:targetSdk 升级要注意什么? 每个版本有行为变更需适配,而且很多只对达到对应 targetSdk 的 App 生效。重点排查权限、存储、后台启动、通知、PendingIntent、前台服务类型、隐式 Intent 等;做法是对照官方 behavior changes 建清单,搜索代码命中点,加兼容分支和回归测试,灰度观察崩溃/ANR/权限拒绝率。