Android Gradle之详解(二)

1,936 阅读6分钟

Gradle详解

介绍Gradle配置中常用的属性信息,具体以配置文件注释形式展现

项目setting.gradle.kts

  • 负责配置和定义项目模块结构,位于项目根目录
// Composing builds插件,引入自定义的插件
includeBuild("plugin_version")

// 配置Gradle插件的仓库源,plugins块中引用插件时,从这些仓库地址下载插件,对所有项目有效
pluginManagement {
    repositories {
        google()
        mavenCentral()
        gradlePluginPortal()
        
        // 非google和maven仓在此添加
        maven { setUrl("https://jitpack.io") }
//        maven(url = "https://jitpack.io")
    }
}

// 配置项目依赖的仓库源,dependencies块中声明的依赖,从这些仓库地址下载依赖,统一管理多项目仓库配置
dependencyResolutionManagement {
    // 控制仓库行为
    // FAIL_ON_PROJECT_REPOS:项目中不允许其他的仓库配置项,有则构建时会报错
    // PREFER_PROJECT:项目中若有其他的仓库配置项,这些仓库会被加到解析依赖时使用的仓库列表中
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        
        // 非google和maven仓在此添加
        maven { setUrl("https://jitpack.io") }
//        maven(url = "https://jitpack.io")
    }
}

rootProject.name = "X"
include(":app")

项目build.gradle.kts

  • 负责管理插件,通过id和版本号指定插件,位于项目根目录
plugins {
    // 包含构建Android应用所有必要功能的插件:编译代码、打包资源、签名apk等
    id("com.android.application") version "8.2.1" apply false

    // 用于支持kotlin的插件,包含编译kotlin代码、kotlin语言的特性支持等
    id("org.jetbrains.kotlin.android") version "1.9.20" apply false

    // 构建Android Lib的插件,不会生成apk,而是生成aar,包含编译代码和资源的库文件
    id("com.android.library") version "8.2.1" apply false

    // 一种比kapt更高效的注解,启用KSP:1.9.22-1.0.16
    id("com.google.devtools.ksp") version "1.9.20-1.0.14" apply false
}

模块build.gradle.kts

  • 负责配置特定模块的构建配置,位于模块根目录
// 导入plugin_version依赖类(自定义依赖管理插件)
import com.android.build.gradle.internal.api.BaseVariantOutputImpl
import com.dcxing.plugin.version.*
import java.text.SimpleDateFormat
import java.util.Date

// 依赖其他gradle值
// apply(from = rootProject.file("config.gradle"))

// apply plugin
//apply(plugin = "com.android.application")

// 额外变量定义
// extra["test_version"] = "1.0.0"
// val test_version by extra("1.0.0")

/* --------------- 主体配置 --------------- */
// 模块依赖插件配置
plugins {
    // 标识该模块为应用模块,区别于library模块
    id("com.android.application")

    // 支持kotlin的插件
    id("org.jetbrains.kotlin.android")

    // 导入plugin_version插件
    id("com.dcxing.plugin.version")

    // kapt
//    id("kotlin-kapt")

    // 启用KSP
    id("com.google.devtools.ksp")

    // 序列化插件
    id("kotlin-parcelize")
}

// 属性定义
val buildTime: String = SimpleDateFormat("yyMMdd").format(Date().time)

// 构建过程中,所添加的依赖Gradle会先从本地检索,找不到才会去远程仓库查找,找到后会缓存至本地,默认缓存24小时,可加快下次构建速度,避免非必要网络下载
configurations.all {
    // 动态版本缓存时效
    resolutionStrategy.cacheDynamicVersionsFor(120, "minutes")

    // 快照版本缓存时效
    resolutionStrategy.cacheChangingModulesFor(24, "hours")
}

val resDirs = arrayOf(
    "ui/toolbar",
    "ui/home"
)

android {
    // 命名空间,一般与包名一致
    namespace = AndroidConfig.namespace_x
    
    // sdk编译版本
    compileSdk = AndroidConfig.compileSdkVersion

    defaultConfig {
        // 包名
        applicationId = AndroidConfig.applicationId
        
        // 最小适配机型,例26,最小适配至Android 8.0
        minSdk = AndroidConfig.minSdkVersion
        
        // 最大适配机型,例35,最大适配至Android 15.0
        targetSdk = AndroidConfig.targetSdkVersion
        
        // 版本号,一般应用于升级场景,升级+1,或按修改次数调整大小
        versionCode = AndroidConfig.versionCode
        
        // 版本名,会展示在设置 - 应用列表 - 应用信息中
        versionName = AndroidConfig.versionName

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

        // ARouter
        ksp {
            arg("AROUTER_MODULE_NAME", project.name)
        }
//        kapt {
//            arguments {
//                arg("AROUTER_MODULE_NAME", project.name)
//            }
//        }
//        javaCompileOptions {
//            annotationProcessorOptions {
//                argument("AROUTER_MODULE_NAME", project.name)
//            }
//        }

        // 统一依赖配置信息,例强制okhttp版本
        configurations.all {
            // com.squareup.okhttp3:okhttp:4.12.0
            // 方式1
//            resolutionStrategy {
//                force("com.squareup.okhttp3:okhttp:4.12.0")
//            }

            // 方式2
//            resolutionStrategy.force(
//                "com.squareup.okhttp3:okhttp:4.12.0"
//            )

            // 方式3
//            resolutionStrategy.eachDependency {
            // 遍历Configuration下所有依赖,匹配修改最终依赖版本号
//                if (requested.group == "com.squareup.okhttp3"
//                    && requested.name == "okhttp") {
//                    useVersion("4.12.0")
//                }
//            }

//            resolutionStrategy {
            // 开启版本冲突报错模式,可直接查看冲突日志
//                failOnVersionConflict()
//            }
        }
    }

    // 定义构建类型,debug或release
    buildTypes {
        release {
            // 构建时禁用用res/drawable自动压缩,提高编译速度
//            isCrunchPngs = false

            // 开启混淆,代码压缩
            isMinifyEnabled = true

            // 移除无用资源
            isShrinkResources = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
            
            // 获取release buildTypes
            signingConfig = signingConfigs.getByName("release")

            // 定义BuildConfig变量,可配置基础url
            buildConfigField("Int", "BUILD_TIME", buildTime)
            buildConfigField("Boolean", "IS_RELEASE", "true")
            buildConfigField("String", "BASE_URL", "https://xxx.api")

            // 可配置apk生成文件名
            android.applicationVariants.all {
                outputs.all {
                    val output: BaseVariantOutputImpl = this as BaseVariantOutputImpl
//                    output.outputFileName = "${name}-${versionName}-${versionCode}-(${buildTime}).aab"
                    output.outputFileName = "${name}-${versionName}-${versionCode}-(${buildTime}).apk"
                }
            }
        }

//        create("dev") {
//            // 复制buildTypes为release的配置
//            initWith(getByName("release"))
//
//            // 清占位符
//            manifestPlaceholders["hostName"] = "com.dcxing.x.dev"
//
//            // 包名加后缀
//            applicationIdSuffix = ".dev"
//        }
    }

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }

    buildFeatures {
        // 启用ViewBinding
        viewBinding = true

        // 默认不生成BuildConfig,修改如下即可
        buildConfig = true

        // 创建aidl文件,默认是置灰的
        aidl = true
    }

    // 配置额外资源目录,便于管理资源,实现资源分包目的
    sourceSets["main"].apply {
        resDirs.forEach {
            res.srcDirs("src/main/java/com/dcxing/draw/$it/res")
        }

        // 配置资源目录,在main目录下新建特性资源文件夹便于管理维护资源,实现资源分包目的
        /*res.srcDirs(
            "src/main/res",
            "src/main/res_core",
            "src/main/res_home"
        )*/
    }

    // app风味,可打不不同渠道的apk
    flavorDimensions.add("app")
    productFlavors {
        create("dev") {
            dimension = "app"

            // 指定资源
            resourceConfigurations.add("en")
            resourceConfigurations.add("xxxhdpi")
//            buildConfigField("Boolean", "IS_DEBUG", "true")
//            buildConfigField("String", "BASE_URL", "https://api.xxxserver.com")
        }

        create("version") {
            dimension = "app"

            // 引用base模块定义的flavorDimensions.add("base"),dev渠道配置
            missingDimensionStrategy("base", "dev")
        }
    }

    // 签名配置
    signingConfigs {
        create("dev") {
            storeFile = File(Signing.devStoreFile)
//            storeFile = file("../dev.jks")
            storePassword = Signing.devStorePassword
//            storePassword = System.getenv("KEY_ALIAS")
            keyAlias = Signing.devKeyAlias
            keyPassword = Signing.devKeyPassword
        }

        create("version") {
            keyAlias = Signing.keyAlias
            keyPassword = Signing.keyPassword
            storeFile = File(Signing.storeFile)
            storePassword = Signing.storePassword
        }
    }

    // 禁用名称包含dev的task
//    gradle.taskGraph.whenReady {
//        tasks.forEach { task ->
//            if (task.name.contains("dev")) {
//                task.enabled = false
//            }
//        }
//    }

    // 打包时排除选项
//    packagingOptions.resources.excludes.add("META-INF/LICENSE.txt")
//    packagingOptions.resources.excludes.add("META-INF/LICENSE.txt")
//    packagingOptions {
//        resources {
//            excludes += setOf("META-INF/LICENSE.txt", "META-INF/LICENSE.txt")
//        }
//    }

    // 单元测试配置
//    testOptions {
//        unitTests {
//            isIncludeAndroidResources = true
//        }
//    }
}

dependencies {
    // 本地jar或aar依赖
    implementation fileTree(includes: ['*.?ar'], dir: 'libs')

    // 依赖本地库
    implementation(project(":base"))

    // 依赖远程库
    implementation(Lib.core_ktx)
    implementation(Lib.appcompat)
    ...

    implementation(Lib.arouter) {
        // 排除依赖包中的传递项,true or false
//        isTransitive = false

        // 排除依赖,可解决依赖冲突问题
        // 可使用如下命令查看依赖树:./gradlew app:dependencies > dependencies.txt
        exclude(group = "com.android.support", module = "support-v4")
    }
    
    // ksp 注解
    ksp(Lib.arouter_compiler_lib)

    ...
}

// kts创建新任务,删除项目build目录
tasks.register("clean", Delete::class) {
    delete(rootProject.buildDir)
}

dependencies小解

依赖项配置参数

  • 默认情况下,Gradle会使用各模块中最高版本号依赖
1. 在同一模块下依赖不同版本,Gradle会选择版本号最高的一个
app模块 --依赖--> retrofit2(2.9.0) --依赖--> okhttp3(4.12.0)
app模块 --依赖--> glide(4.16.0) --依赖--> okhttp3(4.13.0)
app模块 --依赖--> okhttp3(4.12.0)
则最终app依赖的okhttp3版本为`4.13.0`

2. 在不同模块下依赖不同版本,Gradle会选择版本号最高的一个
app模块 --依赖--> retrofit2(2.9.0) --依赖--> okhttp3(4.12.0)
app模块 --依赖--> glide(4.16.0) --依赖--> okhttp3(4.13.0)
app模块 --依赖--> okhttp3(4.12.0)
app模块 --依赖--> network模块 --依赖--> okhttp3(4.14.0)
则最终app依赖的okhttp3版本为`4.14.0`
  • 解决依赖冲突可使用exclude排除依赖(只对当前依赖有效),以消除传递依赖的影响
  • 可使用strictly强制依赖版本,存在强制依赖时,若app和其他模块都有强制依赖,以app模块为准,但app强制依赖的版本不能大于模块强制依赖的版本

implementation 或 xxxImplementation(多渠道打包时使用)

  • 此种方式添加依赖,在编译时不会将该模块的依赖泄露给其依赖者
  • 具有隐藏依赖的效果,防止依赖传递,减少不必要的依赖检查和处理,提高编译速度和改善构建性能
  • 此方式替代api依赖可缩短构建时间(减少了构建模块数,使用api依赖时关联此模块的其他模块也会参与编译)
  • A -> B -> C,则A不能引用C中代码
// 依赖jar or aar
implementation(files("libs/xxx.jar"))
implementation(fileTree(baseDir = "libs") { "*.jar" })
implementation(fileTree(baseDir = "libs") { "*.?ar" })

// 依赖module
// 需在setting.gradle.kts中声明 -> include(":app")
implementation(project(":base"))

// 依赖远程库,两种方式
// 1. implementation("com.google.code.gson:gson:2.10.1")
// 2. implementation(group = "com.google.code.gson", name = "gson", version = "2.10.1")
implementation(Lib.arouter) {
    // 排除依赖
    exclude(group = "com.android.support", module = "support-v4")
}

// 强制指定依赖版本,两种方式
implementation("com.google.code.gson:gson:2.10.1!!")
implementation(com.google.code.gson:gson) {
    version { 
        strictly("2.10.1")
    }
}

api

  • 此种方式添加依赖,在编译时会将该模块的依赖不仅模块内部可用,依赖其的其他模块也可引用
  • 使用api添加依赖会增加构建时间
  • A -> B -> C,则A可以直接引用C中代码
api(Lib.gson)

小结

  1. setting.gradle.kts负责配置和定义项目模块结构
  2. 项目build.gradle.kts负责管理插件,通过id和版本号指定插件包括plugins块
  3. 模块build.gradle.kts负责配置特定模块的构建配置,包括plugins块、android块、dependencies块