Gradle使用技巧(Android场景)

发布于:2025-09-11 ⋅ 阅读:(16) ⋅ 点赞:(0)

总览

本文围绕 “**配置 → 任务 → 动态化 → 依赖 → 本地库 → 资源 → 自动化**” 七大主题,把日常高频痛点一次讲透。

阅读方式:

  1. 先通读“思路”了解为什么;

  2. 再抄“最小可运行示例”快速验证;

  3. 最后看“延伸”拓展到多项目/组件化。

1 全局配置(Root Project)

目标:一次声明、所有子模块(application / library / test)全部生效,避免每个 build.gradle 反复复制。

1.1 统一 UTF-8(编译期 + 控制台)

// root/gradle.properties
file.encoding=UTF-8
org.gradle.jvmargs=-Xmx4g -Dfile.encoding=UTF-8
org.gradle.console=rich           // 彩色输出
// root/build.gradle.kts
allprojects {
    tasks.withType<JavaCompile>().configureEach {
        options.encoding = "UTF-8"
    }
    tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
        kotlinOptions {
            jvmTarget = "17"
            // 统一 Kotlin 编码
            freeCompilerArgs += listOf("-Xjsr305=strict", "-Xemit-jvm-type-annotations")
        }
    }
}

1.2 依赖 Google/MavenCentral 仓库(一次声明)

// root/settings.gradle.kts
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) // 强制收敛
    repositories {
        google()
        mavenCentral()
        // 公司私有仓库
        maven("https://nexus.xxx.com/repository/android-public/")
    }
}

1.3 支持 Groovy(混合遗留脚本)

// root/buildSrc/build.gradle.kts
plugins {
    `groovy-gradle-plugin`   // 预编译 Groovy 脚本插件
}

把旧 Groovy 脚本放到 root/buildSrc/src/main/groovy 即可被 Kotlin 引用。

1.4 定义全局变量(版本号、SDK、编译参数)

推荐 **Version Catalogs**(Gradle 7.0+ 官方方案):

# root/gradle/libs.versions.toml
[versions]
compileSdk = "34"
minSdk = "24"
targetSdk = "34"
agp = "8.3.0"
kotlin = "1.9.20"
retrofit = "2.9.0"

[libraries]
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }

[bundles]
network = ["retrofit"]

任何模块:

android {
    compileSdk = libs.versions.compileSdk.get().toInt()
}
dependencies {
    implementation(libs.bundles.network)
}

旧项目可用 buildSrc/src/main/kotlin/Config.kt 单文件枚举,但维护成本高,不推荐。

1.5 配置 Lint(全局选项 + 自定义规则)

// root/build.gradle.kts
subprojects {
    afterEvaluate {
        extensions.findByType<com.android.build.api.dsl.CommonExtension<*, *, *, *, *>>()?.apply {
            lint {
                abortOnError = false  //有lint错误也不停止构建
                checkReleaseBuilds = false //禁用在发布构建时进行lint检查
                disable += "InvalidPackage"   // 忽略跨平台 RN 警告
                baseline = file("lint-baseline.xml") //lint检查的基准文件
            }
        }
    }
}

2 操控 Task(AGP 生成的标准任务)

所有 Android 任务继承自 Task,名字/类型/顺序均可改。

2.1 更改输出 APK 名字(含版本、渠道、时间)

android.applicationVariants.all {
    val variant = this
    variant.outputs.all {
        val apkName = "App-${variant.flavorName}-${variant.buildType.name}-${variant.versionName}-${gitCommitShort()}.apk"
        (this as com.android.build.gradle.internal.api.BaseVariantOutputImpl).outputFileName = apkName
    }
}

gitCommitShort() 为自定义函数,返回当前 commit 前 7 位。

2.2 更改 AAR 输出目录(方便 CI 收集)

android.libraryVariants.all {
    val variant = this
    variant.packageLibraryProvider.configure {
        val dest = rootProject.layout.buildDirectory.dir("outputs/aar/${variant.name}")
        destinationDirectory.set(dest)
    }
}

2.3 跳过 AndroidTest(CI 加速)

android.libraryVariants.all {
    val variant = this
    variant.packageLibraryProvider.configure {
        val dest = rootProject.layout.buildDirectory.dir("outputs/aar/${variant.name}")
        destinationDirectory.set(dest)
    }
}

或在命令行:

./gradlew assembleDebug -x testDebugUnitTest -x connectedDebugAndroidTest

2.4 找出耗时的 Task(构建速度分析)

./gradlew assembleDebug --profile --scan

Gradle 会生成 build/reports/profile/... 火焰图;

或配置 buildSrc 插件:

gradle.taskGraph.afterTask {
    if (this is TaskExecutionListener) {
        val cost = endTime - startTime
        if (cost > 1000) println("Slow task: $path took ${cost}ms")
    }
}

2.5 抽离 Task 脚本(可复用)

  1. buildSrc/src/main/kotlinApkRenamePlugin.kt

  2. 继承 Plugin<Project>,内部注册 task;

  3. 任何模块 plugins { id("apk-rename") } 即可。


    3 动态化(Build-Time 黑科技)

    3.1 动态设置 BuildConfig

    android.defaultConfig {
        buildConfigField("String", "API_URL", "\"${findProperty("API_URL") ?: "https://api.example.com"}\"")
        buildConfigField("long", "BUILD_TIMESTAMP", "${System.currentTimeMillis()}L")
    }

    通过 gradle.properties 或 CI 环境变量注入,无需改代码。

    3.2 填充 Manifest 占位符

    android.defaultConfig {
        manifestPlaceholders["UMENG_CHANNEL"] = "default"
    }
    productFlavors {
        create("xiaomi") { manifestPlaceholders["UMENG_CHANNEL"] = "xiaomi" }
    }

    Manifest:

    <meta-data android:name="UMENG_CHANNEL"
               android:value="${UMENG_CHANNEL}" />

    3.3 让 buildType 支持继承(减少重复)

    android.buildTypes {
        create("staging") {
            initWith(getByName("debug"))
            isDebuggable = false
            applicationIdSuffix = ".stg"
        }
    }

    3.4 让 Flavor 支持继承(使用 dimension + fallback

    flavorDimensions += listOf("api")
    productFlavors {
        create("api19") { dimension = "api" }
        create("api21") { dimension = "api" }
        create("free")  { dimension = "tier" }
        create("paid")  { dimension = "tier" }
    }
    android.variantFilter {
        if (flavors.any { it.name == "api19" } && flavors.any { it.name == "paid" }) {
            ignore = true   // 屏蔽 api19+paid 组合
        }
    }

    3.5 内测版本用特定 Icon

    sourceSets {
        getByName("debug") {
            res.srcDirs("src/debug/res")
        }
    }

    src/debug/res/mipmap-xxxhdpi/ic_launcher.png 放内测图标即可。

    3.6 不同渠道不同包名

    productFlavors {
        create("huawei")  { applicationId = "com.xxx.huawei"  }
        create("xiaomi")  { applicationId = "com.xxx.xiaomi"  }
    }

    3.7 自动填充版本信息(语义化 + 自增)

    // buildSrc/src/main/kotlin/Versioning.kt
    object Versioning {
        val major = 3
        val minor = 2
        val patch = gitCommitCount()  // git rev-list --count HEAD
        val versionName = "$major.$minor.$patch"
        val versionCode = major * 10000 + minor * 100 + patch
    }
    android.defaultConfig {
        versionCode = Versioning.versionCode
        versionName = Versioning.versionName
    }

    4 远程依赖(Maven)

    4.1 配置私有 Maven

    repositories {
        maven {
            url = uri("https://nexus.xxx.com/repository/maven-releases/")
            credentials {
                username = findProperty("NEXUS_USER") as String? ?: System.getenv("NEXUS_USER")
                password = findProperty("NEXUS_PASS") as String? ?: System.getenv("NEXUS_PASS")
            }
        }
    }

    4.2 依赖相关 API 速查

    配置名

    含义

    implementation

    编译 & 运行期都参与打包,但不传递

    api

    编译 & 运行期都参与,且传递

    compileOnly

    仅编译,不打包(如 Provided)

    runtimeOnly

    仅运行期

    testImplementation

    单测

    androidTestImplementation

    插桩测

    4.3 组合依赖(Bundles)

    见 1.4 Version Catalogs 的 bundles.network

    4.4 依赖传递 & 禁用

    implementation(libs.okhttp) {
        isTransitive = false   // 关闭传递
    }

    4.5 动态版本号(慎用)

    implementation("com.facebook.soloader:soLoader:0.10.+")

    每次构建都可能变,CI 必须 --refresh-dependencies 才能复现,建议锁住版本。

    4.6 强制版本号(统一 BOM)

    // 先导入 BOM
    implementation(platform("com.squareup.okhttp3:okhttp-bom:4.12.0"))
    // 再写依赖,无需版本
    implementation("com.squareup.okhttp3:okhttp")
    implementation("com.squareup.okhttp3:logging-interceptor")

    4.7 exclude 关键字(解决冲突)

    implementation(libs.facebook.react) {
        exclude(group = "com.android.support", module = "support-annotations")
    }

    4.8 依赖管理(锁版本)

    ./gradlew dependencies --write-locks   // 生成 gradle.lockfile

    CI 开启:

    // root/gradle.properties
    dependency.verification=strict


    5 本地依赖(非 Maven 网络)

    5.1 引用 AAR(单文件)

    xxx.aar 放到 libs/ 目录:

    dependencies {
        implementation(files("libs/xxx.aar"))
    }

    多 AAR 循环:

    implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.aar"))))

    5.2 依赖 Module / Jar

    implementation(project(":base-lib"))
    implementation(files("libs/legacy.jar"))
    
    

    5.3 自建本地 Maven 仓库(发布到目录)

    // library/build.gradle.kts
    plugins {
        `maven-publish`
    }
    publishing {
        repositories {
            maven {
                url = uri("${rootProject.projectDir}/repo")
            }
        }
        publications {
            create<MavenPublication>("release") {
                from(components["release"])
                groupId = "com.xxx"
                artifactId = "base-lib"
                version = "1.0.0"
            }
        }
    }

    其他工程:

    repositories {
        maven(uri("${rootProject.projectDir}/repo"))
    }
    implementation("com.xxx:base-lib:1.0.0")

    5.4 本地依赖 React Native(源码集成)

    // settings.gradle.kts
    include(":react-native")
    project(":react-native").projectDir = file("../node_modules/react-native/android")
    dependencies {
        implementation(project(":react-native"))
    }

    5.5 重新打包第三方 Jar(改名 / 移类 / shade)

    // buildSrc 插件,使用 Shadow 插件
    plugins {
        id("com.github.johnrengelman.shadow") version "8.1.1"
    }
    tasks.shadowJar {
        archiveClassifier.set("")
        relocate("com.google.gson", "com.xxx.shaded.gson")
    }

    6 资源管理(精简 / 白牌 / 多语言)

    1. 只打包指定语言:

        android {
            resourceConfigurations += listOf("zh", "en")
        }

      1. 移除无用资源(与 ProGuard 联动):

          buildTypes.getByName("release") {
              isShrinkResources = true
              isMinifyEnabled = true
              proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
          }

        1. 多 DPI 白牌:

          productFlavors {
              create("lite") {
                  ndk {
                      abiFilters += listOf("armeabi-v7a")
                  }
              }
          }


          7 总结 & 常见问题速查

          需求

          一句话答案

          自动 versionCode/versionName

          用 git commit count + 语义化脚本(见 3.7)

          强制统一某个库版本

          用 BOM + platform()(见 4.6)

          管理签名

          keystore.jksroot/keys/,在 local.properties 填密码,CI 用环境变量注入

          引入本地 Maven

          maven(uri(...))(见 5.3)

          本地依赖写法

          files() / fileTree() / project()

          制作本地库

          publishing 插件发布到目录

          Exclude 冲突

          exclude(group=, module=) / 全局 configurations.all { exclude ... }

          两个库类冲突

          ① 用 exclude 去掉其中一个;② 用 shadow 重定位包名;③ 升级冲突方到统一版本


          8 附录:速查脚本(直接复制)

          1. 一键清理所有中间产物 + 重新生成:

            ./gradlew clean assembleDebug --refresh-dependencies --no-build-cache --no-configuration-cache

            1. 输出所有依赖到文件:

              ./gradlew :app:dependencies > deps.txt
              
              
              1. 查看构建扫描(需同意条款):

                ./gradlew assembleDebug --scan
                
                


                至此,从“全局 UTF-8”到“动态版本号”到“本地 Maven”再到“依赖冲突”的完整链路全部覆盖。

                把本文示例按需粘贴到你的项目,即可在 30 分钟内拥有“可维护、可追踪、可 CI”的 Gradle 工程骨架。祝编译极速、依赖清爽!

                参考链接:

                https://developer.android.com/build/gradle-build-overview?hl=zh-cn


                网站公告

                今日签到

                点亮在社区的每一天
                去签到