Android Gradle 笔记

一、Gradle 基础认知

1.1 Gradle 是什么?

Gradle 是一个基于 Groovy 或 Kotlin DSL 的自动化构建工具,它结合了 Ant 的灵活性和 Maven 的依赖管理能力,并且引入了"约定优于配置"的理念。在 Android 项目中,Google 提供了 Android Gradle Plugin (AGP),它与 Gradle 协同工作,专门用于构建 Android 应用。

1.2 核心概念

  • Project:一个 Gradle 构建对应一个或多个 Project。在 Android 中,根目录是一个 Project,每个模块(如 app、library)也是一个 Project。
  • Task:构建的最小工作单元,例如编译 Java 代码、打包资源、生成 APK 等。Gradle 通过 Task 依赖图来执行构建。
  • Plugin :插件封装了通用的构建逻辑,如 com.android.application 插件提供了构建 App 所需的所有 Task 和配置。
  • Build Lifecycle :Gradle 构建分为三个阶段:
    1. 初始化:解析 settings.gradle,确定哪些 Project 参与构建。
    2. 配置:执行每个 Project 的 build.gradle,生成 Task 依赖图。
    3. 执行:根据命令行参数执行指定的 Task。

1.3 Android 项目结构

一个典型的 Android 项目包含以下 Gradle 文件:

  • settings.gradle:声明项目包含的模块。
  • build.gradle (Project level):全局配置,如仓库地址、插件依赖、全局变量。
  • build.gradle (Module level):每个模块的具体配置,如 android 闭包、依赖项。
  • gradle.properties:JVM 参数、Gradle 属性(如并行编译开关)。
  • gradle-wrapper.properties:指定 Gradle 版本和下载地址。

二、Android Gradle Plugin 核心配置

2.1 基本配置

在模块的 build.gradle 中,android 闭包是我们最常打交道的区域:

groovy 复制代码
android {
    compileSdk 34
    buildToolsVersion "34.0.0"

    defaultConfig {
        applicationId "com.example.myapp"
        minSdk 21
        targetSdk 34
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    // 签名配置
    signingConfigs {
        release {
            storeFile file("keystore.jks")
            storePassword "123456"
            keyAlias "key"
            keyPassword "123456"
        }
    }

    // 构建类型
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.release
        }
        debug {
            minifyEnabled false
            debuggable true
        }
    }

    // 产品变种(多渠道)
    productFlavors {
        free {
            dimension "version"
            applicationIdSuffix ".free"
            versionNameSuffix "-free"
        }
        pro {
            dimension "version"
            applicationIdSuffix ".pro"
            versionNameSuffix "-pro"
        }
    }

    // 源集配置(可自定义代码和资源目录)
    sourceSets {
        main {
            java.srcDirs = ['src/main/java']
            res.srcDirs = ['src/main/res']
        }
        free {
            java.srcDirs = ['src/free/java']
        }
    }

    // 编译选项
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    // Kotlin 选项(如果使用 Kotlin)
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

2.2 Build Variants(构建变种)

构建变种 = Build Type (如 debug/release) × Product Flavor (如 free/pro)。每个变种都有自己的输出 APK 名称、代码和资源源集(src/freeDebug/)。使用变种可以实现同一套代码生成不同版本的应用(如免费版/付费版、不同渠道包)。

2.3 依赖管理

2.3.1 依赖配置类型

  • implementation:只在当前模块内部使用,不会泄露给其他模块。推荐首选。
  • api:将依赖暴露给其他模块,适用于公共库。
  • compileOnly:仅编译时使用,不打包进 APK(如注解处理器)。
  • runtimeOnly:仅运行时需要,编译时不需要。
  • annotationProcessor / kapt:注解处理器。
  • testImplementation:仅用于单元测试。
  • androidTestImplementation:仅用于 Android 测试。

2.3.2 依赖传递与排除

groovy 复制代码
dependencies {
    implementation('com.example:library:1.0') {
        exclude group: 'com.unwanted', module: 'unwanted-module'
        transitive = false // 禁止传递依赖
    }
}

2.3.3 版本冲突解决

Gradle 默认会选择最高版本,但可能会遇到冲突。可以通过以下方式强制指定版本:

groovy 复制代码
configurations.all {
    resolutionStrategy {
        force 'com.squareup.okhttp3:okhttp:4.11.0'
        // 或者动态选择策略
        failOnVersionConflict()
    }
}

三、实际使用案例

案例1:统一版本管理

随着项目模块增多,手动维护版本号容易出错。我们通常采用以下几种方式统一管理:

方式一:使用 ext 全局变量(传统)

在项目根 build.gradle 中定义:

groovy 复制代码
ext {
    compileSdkVersion = 34
    minSdkVersion = 21
    targetSdkVersion = 34
    versionCode = 1
    versionName = '1.0'

    // 依赖版本
    androidxAppcompat = '1.6.1'
    retrofit = '2.9.0'
}

然后在模块中引用:

groovy 复制代码
android {
    compileSdk rootProject.ext.compileSdkVersion
    // ...
}

dependencies {
    implementation "androidx.appcompat:appcompat:$rootProject.ext.androidxAppcompat"
}

方式二:Gradle Version Catalog(推荐,Gradle 7.0+)

创建 gradle/libs.versions.toml 文件:

toml 复制代码
[versions]
compileSdk = "34"
minSdk = "21"
targetSdk = "34"
appcompat = "1.6.1"

[libraries]
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }

在模块中:

kotlin 复制代码
dependencies {
    implementation(libs.androidx.appcompat)
}

并可结合 buildSrc 或复合构建实现更高级的复用。

案例2:多渠道打包(友盟统计)

以友盟渠道为例,通常需要为每个渠道生成不同的 APK,并在 AndroidManifest.xml 中替换渠道号。

配置 productFlavors

groovy 复制代码
flavorDimensions "channel"
productFlavors {
    google {
        dimension "channel"
    }
    huawei {
        dimension "channel"
    }
    xiaomi {
        dimension "channel"
    }
}

// 批量配置,避免重复
productFlavors.all { flavor ->
    // 为每个 flavor 添加一个 manifest placeholder
    flavor.manifestPlaceholders = [UMENG_CHANNEL: name]
}

AndroidManifest.xml 中使用占位符:

xml 复制代码
<meta-data
    android:name="UMENG_CHANNEL"
    android:value="${UMENG_CHANNEL}" />

生成不同应用名或图标 :可以在对应 flavor 的源集目录(如 src/google/res/values/strings.xml)中覆盖字符串资源。

案例3:自定义构建配置(如自动生成 BuildConfig 字段)

有时候我们需要在代码中获取一些构建时信息,比如 Git 提交号、编译时间。可以通过在 build.gradle 中动态添加 BuildConfig 字段实现:

groovy 复制代码
android {
    defaultConfig {
        buildConfigField "String", "GIT_COMMIT", "\"${getGitCommit()}\""
        buildConfigField "long", "BUILD_TIME", "${System.currentTimeMillis()}L"
    }
}

def getGitCommit() {
    try {
        def stdout = new ByteArrayOutputStream()
        exec {
            commandLine 'git', 'rev-parse', '--short', 'HEAD'
            standardOutput = stdout
        }
        return stdout.toString().trim()
    } catch (Exception e) {
        return "unknown"
    }
}

然后在 Java/Kotlin 代码中通过 BuildConfig.GIT_COMMIT 访问。

案例4:自定义 Task 实现自动化

假设我们需要在编译前自动生成一个版本信息文件,可以编写自定义 Task:

groovy 复制代码
task generateVersionFile {
    doLast {
        def versionFile = new File("$buildDir/generated/version/version.txt")
        versionFile.parentFile.mkdirs()
        versionFile.text = """
            Version Name: ${android.defaultConfig.versionName}
            Version Code: ${android.defaultConfig.versionCode}
            Build Time: ${new Date()}
        """.trim()
    }
}

// 让预编译任务依赖此 Task
preBuild.dependsOn generateVersionFile

如果想让生成的资源能被代码访问,可以将其添加到源集:

groovy 复制代码
android.sourceSets.main.assets.srcDirs += "$buildDir/generated/version"

案例5:优化构建速度

大型项目中构建速度是痛点。我们可以做以下优化:

  1. 启用 Gradle 构建缓存(本地和远程):

    properties 复制代码
    # gradle.properties
    org.gradle.caching=true
  2. 启用并行编译和配置缓存

    properties 复制代码
    org.gradle.parallel=true
    org.gradle.configuration-cache=true
  3. 使用最新版 Gradle 和 AGP:每个新版本都包含性能改进。

  4. 按需配置 :使用 includeBuild 替代 project 依赖,或在开发时只加载需要的模块。

  5. 避免动态依赖版本 :如 + 会导致每次检查新版本,应指定固定版本。

  6. 分析构建耗时 :运行 ./gradlew build --scan 生成构建报告,分析瓶颈。

  7. 优化自定义 Task:确保自定义 Task 正确配置输入输出,避免不必要的重复执行。

案例6:多模块依赖管理

在大型组件化项目中,模块众多,依赖关系复杂。我们常用以下几种方式简化:

  • 定义依赖版本常量:如前所述。
  • 使用 BOM(Bill of Materials):如 Firebase BOM,统一管理一组库的版本。
  • 自定义 Gradle 插件:封装通用配置,避免在每个模块重复写相同的代码。

四、常见问题与解决方案

4.1 依赖冲突

现象Duplicate classDexArchiveMergerException
排查 :运行 ./gradlew :app:dependencies 查看依赖树,找出冲突。
解决 :使用 excludeforce 指定版本。

4.2 构建缓慢

  • 检查是否使用了动态版本。
  • 检查自定义 Task 是否正确地使用了 @Input@Output 注解。
  • 使用 --parallel--configure-on-demand

4.3 Manifest 合并错误

现象Manifest merger failed
解决 :在 AndroidManifest 中添加 tools:replace 属性,或在模块的 build.gradle 中设置 android.defaultConfig.manifestPlaceholders

4.4 Gradle 版本兼容性

AGP 对 Gradle 版本有严格对应关系(见 官方文档)。升级时要同时匹配。

4.5 多模块间资源冲突

现象 :资源 ID 重复。
解决 :为每个模块设置 resourcePrefix "module_"


五、进阶:编写自定义 Gradle 插件

当项目足够大,通用的配置和 Task 越来越多,可以将其抽取成自定义插件,便于复用和版本管理。

5.1 插件实现方式

  • buildSrc :在项目根目录创建 buildSrc 模块,自动被编译为插件,但变更后需重新构建整个项目。
  • 独立插件项目:发布到 Maven 仓库供多个项目使用。

5.2 简单示例(buildSrc 方式)

  1. 创建 buildSrc/src/main/java/com/example/MyPlugin.groovy(或 Kotlin):
groovy 复制代码
import org.gradle.api.Plugin
import org.gradle.api.Project

class MyPlugin implements Plugin<Project> {
    void apply(Project project) {
        project.task('myTask') {
            doLast {
                println "Hello from MyPlugin in ${project.name}"
            }
        }
    }
}
  1. buildSrc/build.gradle 中配置:
groovy 复制代码
apply plugin: 'groovy'
dependencies {
    implementation gradleApi()
    implementation localGroovy()
}
  1. 在其他模块中应用插件:
groovy 复制代码
apply plugin: com.example.MyPlugin

5.3 高级用法:扩展

插件通常提供 DSL 扩展,让用户配置。例如:

groovy 复制代码
class MyPluginExtension {
    String message = "default"
}

class MyPlugin implements Plugin<Project> {
    void apply(Project project) {
        def extension = project.extensions.create('myConfig', MyPluginExtension)
        project.task('myTask') {
            doLast {
                println "Message: ${extension.message}"
            }
        }
    }
}

使用方:

groovy 复制代码
myConfig {
    message = "Hello World"
}

六、总结

Gradle 是 Android 开发中不可或缺的一环,掌握它不仅能让我们高效地管理项目,还能通过自动化解决重复劳动。回顾十年历程,从最初被其陡峭的学习曲线折磨,到如今能够随心所欲地定制构建流程,我深刻体会到:理解 Gradle 的核心概念(Task、Plugin、生命周期)比死记硬背语法更重要。实际项目中,建议从小处着手,逐步引入自动化脚本,不断优化构建速度和依赖管理。

相关推荐
城东米粉儿1 小时前
Android Monkey 笔记
android
城东米粉儿2 小时前
Android 组件化 笔记
android
编程小风筝2 小时前
Android移动端如何实现多线程编程?
android
城东米粉儿2 小时前
Android 模块化 笔记
android
城东米粉儿2 小时前
Android HandlerThread 笔记
android
城东米粉儿3 小时前
Android Condition 笔记
android
肖。35487870943 小时前
html中onclick误区,后续变量会更改怎么办?
android·java·javascript·css·html
城东米粉儿3 小时前
Android 动态加载 Activity
android
城东米粉儿4 小时前
Android lancet 笔记
android